728x90
반응형
SMALL

이번 포스팅에서는 도메인 모델 패턴과 트랜잭션 스크립트 패턴이 무엇인지 알아보고, 어떤 장단점이 있고, 무엇이 언제 더 좋은가?에 대해 고민해보는 포스팅이다. 

 

도메인 모델 패턴

도메인 모델 패턴은, 애플리케이션의 비즈니스 로직을 객체 중심으로 구성하는 패턴이다. 이 패턴에서는 실세계의 개념을 반영한 객체(엔티티)를 만들어 각 객체가 비즈니스 로직과 상태를 자체적으로 관리하도록 한다. 예를 들어, 다음 코드를 보자.

 

Order

package cwchoiit.shoppingmall.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Entity
@Getter
@Table(name = "orders")
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 생성 메서드를 만들었으면 그 메서드로만 인스턴스를 생성할 수 있도록 막아야 함
public class Order {

    @Id
    @GeneratedValue
    @Column(name = "order_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "delivery_id")
    private Delivery delivery;

    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<>();

    private LocalDateTime orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    // ################################################################# //
    // ######################## 연관관계 편의 메서드 ######################## //
    // ################################################################# //
    public void addRelationshipMember(Member member) {
        this.member = member;
        member.getOrders().add(this);
    }

    public void addRelationshipDelivery(Delivery delivery) {
        this.delivery = delivery;
        delivery.addRelationshipOrder(this);
    }

    public void addRelationshipOrderItem(OrderItem orderItem) {
        this.orderItems.add(orderItem);
        orderItem.addRelationshipOrder(this);
    }

    // ################################################################# //
    // ######################## 생성 메서드 ############################### //
    // ################################################################# //
    public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) {
        Order order = new Order();
        order.status = OrderStatus.ORDER;
        order.orderDate = LocalDateTime.now();

        order.addRelationshipMember(member);
        order.addRelationshipDelivery(delivery);
        Arrays.stream(orderItems).forEach(order::addRelationshipOrderItem);

        return order;
    }

    public void cancel() {
        if (delivery.getStatus() == DeliveryStatus.COMP) {
            throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능합니다.");
        }

        status = OrderStatus.CANCEL;
        for (OrderItem orderItem : orderItems) {
            orderItem.cancel();
        }
    }

    // ################################################################# //
    // ######################## 조회 메서드 ############################### //
    // ################################################################# //
    public int getTotalPrice() {
        return orderItems.stream()
                .mapToInt(OrderItem::getTotalPrice)
                .sum();
    }
}
  • 이 코드의 하단부를 보면, 여러 메서드가 있지만 그 중 대표적인 예시로 getTotalPrice()로 설명을 해보겠다. 이 메서드는 주문 가격을 반환하는 메서드이고 주문 가격은 주문 수량 * 주문 상품의 개수의 결과값을 다 합친것이다. OrderItem 이라는 또 하나의 객체가 있지만 그 안에 있는 메서드가 그런 작업을 한다.
  • 여튼, 이런 코드는 서비스 레이어에 주문 메서드에서 한 줄 한 줄 로직을 작성할 수도 있지만, 이렇게 객체 자체적으로 그 메서드를 가지고 있을 수도 있다. 즉, 객체 자체에 주문 생성과 관련된 로직이 포함되어 있다는 말이다.
  • 또 하나의 예로, createOrder()와 같은 메서드도 서비스 레이어에 주문 관련 메서드안에 한 줄 한 줄 비즈니스 로직을 작성할 수도 있지만 이렇게 객체 자체적으로 그 기능을 가지고 있을 수도 있다.  
  • 이게 바로 도메인 모델 패턴이다. 즉, 관련 메서드는 해당 객체가 가지고 있는 것을 말한다.

 

또 다른 예시로는 다음 코드를 보자.

Mp3

package cwchoiit.shoppingmall.domain;

public class Mp3 {
    private int volume;
    private String name;

    public Mp3(int volume, String name) {
        this.volume = volume;
        this.name = name;
    }
    
    public void play() {
        System.out.println(name + " - " + volume + "dB");
    }
    
    public void volumeUp() {
        volume++;
        System.out.println("Volume up to " + volume + "dB");
    }
    
    public void volumeDown() {
        volume--;
        System.out.println("Volume down to " + volume + "dB");
    }
}
  • Mp3 라는 객체가 있고 이 객체 안에는 name, volume 이라는 두 개의 필드가 있다. 
  • 이 객체의 볼륨을 줄이고 키우는 작업은 이 서비스 레이어에서 인스턴스를 만들고 세터를 사용해서 볼륨을 줄이고 키울수도 있겠지만 이렇게 객체 자체적으로 volumeUp(), volumeDown() 메서드를 제공해서 비즈니스 로직 자체를 객체 안에 포함시킬수도 있다.
  • 이게 도메인 모델 패턴이다. 
  • 그리고 이렇게 하면 실세계와 가장 밀접한 객체 지향적 설계가 된다고 본다. Mp3를 사용하는 사용자가 볼륨을 줄이는 행위는 Mp3가 제공하는거지 사용자가 새로이 만들어내는 기술이 아니잖아.

 

도메인 모델 패턴의 장단점

장점

  • 재사용성과 확장성: 객체 간의 관계와 상태가 명확하게 정의되어 있어, 새로운 비즈니스 요구사항이 추가될 때 확장이 쉽다.
  • 유지보수성: 비즈니스 로직이 객체에 분산되어 있어서, 특정 기능을 수정해도 전체 애플리케이션에 미치는 영향이 적다.
  • 객체 지향적: 실세계의 개념을 반영하여 객체를 통해 로직을 처리하므로 이해하기 쉽고, 자바 같은 객체 지향 언어에 적합하다.

단점

  • 복잡성 증가: 복잡한 비즈니스 로직을 가진 대규모 애플리케이션에서는 객체 간의 관계와 상호작용이 복잡해질 수 있다.
  • 초기 개발 비용: 모델을 설계하고 객체를 정의하는 데 시간이 상대적으로 많이 걸린다.

 

 

트랜잭션 스크립트 패턴

트랜잭션 스크립트 패턴은 비즈니스 로직을 절차적으로 처리하는 코드로 구성하는 방식이다. 이 패턴은 비즈니스 로직을 하나의 스크립트로 정의하여 각 요청에 대해 해당 스크립트를 실행하는 구조를 가진다. 예를 들어 OrderService와 같은 서비스 클래스에 메서드 형태로 비즈니스 로직을 구현하는 것이 일반적이다.

 

예를 들어 아래 코드를 보자.

Mp3

package cwchoiit.shoppingmall.domain;

public class Mp3 {
    private int volume;
    private String name;

    public Mp3(int volume, String name) {
        this.volume = volume;
        this.name = name;
    }

    public int getVolume() {
        return volume;
    }

    public void setVolume(int volume) {
        this.volume = volume;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • Mp3 클래스가 있다. 이 클래스는 volume, name 필드를 가지고 있고 Getter, Setter를 가지고 있다.

Mp3Service

package cwchoiit.shoppingmall.service;

import cwchoiit.shoppingmall.domain.Mp3;

public class Mp3Service {
    
    public void mp3() {
        Mp3 mp3 = new Mp3(0, "ipod");
        
        // 볼륨 증가
        mp3.setVolume(1);
        
        // 볼륨 증가 후 로직 처리
        // ...
        
        // 볼륨 감소
        mp3.setVolume(0);
        
        // ...
    }
}
  • Mp3Service는 서비스 클래스이다. 이 클래스의 특정 메서드 안에서 Mp3 인스턴스를 만들고 그 인스턴스의 볼륨을 줄이고 키우고 그 사이에 어떤 비즈니스 로직들이 쭉 스크립트 작성하듯 절차적으로 진행된다.
  • 이게 트랜잭션 스크립트 패턴이다.

 

트랜잭션 스크립트 패턴의 장단점

장점

  • 간단하고 직관적: 코드가 직관적이고 간단해서 빠르게 개발하고 한눈에 코드를 알아보기 쉽다.

단점

  • 재사용성 부족: 비즈니스 로직이 중복될 가능성이 높고, 코드 재사용이 불가능하다.
  • 확장성 부족: 비즈니스 로직이 절차적으로 구성되어 있어 새로운 요구사항이 추가되면 코드가 복잡해지고 유지보수성이 떨어진다.
  • 비즈니스 로직 분리 어려움: 코드가 길어지고 복잡해지면, 로직이 여기저기 흩어져 가독성이 떨어진다.

 

어떤 패턴이 더 좋은가요?

굳이 말하자면, 애플리케이션의 복잡도와 요구사항에 따라 달라진다고 본다. 더 좋고 나쁜 것은 없다고 생각한다.

  • 복잡한 도메인 로직이 있는 경우: 도메인 모델 패턴이 더 적합하다. 객체 지향적인 구조와 비즈니스 로직이 함께 동작하기 때문에 확장성이 높고 유지보수도 쉽기 때문이다.
  • 단순한 애플리케이션이나 빠른 개발이 필요한 경우: 트랜잭션 스크립트 패턴이 더 효율적일 수 있다. 절차적인 코드 구조로 인해 간단한 작업에는 빠르고 효율적이다.

 

내 개인적인 의견으로는, 도메인 모델 패턴이 자바의 장점도 살리면서 객체 지향적이고 장기적으로 볼 때 유지보수성과 확장성, 재사용성을 고려해 더 좋은 선택인 것 같다. 그런데 만약, 클래스 파일도 몇 개 없고 정말 간단한 애플리케이션이라면, 트랜잭션 스크립트 패턴을 사용해도 누가 뭐라고 하진 않을 것 같다. 

728x90
반응형
LIST

'Design Pattern' 카테고리의 다른 글

Template Callback Pattern  (0) 2023.12.12
Template Method Pattern  (2) 2023.12.12

+ Recent posts