새소식

Computer Science/Design Patterns

Domain Model Pattern과 Transaction Script Pattern

 

 

소프트웨어 개발에서 아키텍처 패턴은 애플리케이션의 설계와 구조를 결정하는 데 중요한 역할을 한다.

이번 글에서는 두 가지 주요한 아키텍처 패턴인 도메인 모델 패턴(Domain Model Pattern)과 트랜잭션 스크립트 패턴(Transaction Script Pattern)에 대해 간단하게 알아보고자 한다.

 

 

 

도메인 모델 패턴

 

도메인 모델 패턴은 소프트웨어 시스템의 핵심 개념을 모델링하는 방법론으로, 시스템의 복잡한 비즈니스 로직을 객체 지향적인 방식으로 모델링하여 구현한다.

 

Entity, Service, Repository 등의 요소를 사용하여 비즈니스 도메인을 표현하는데,

Spring Framework의 Spring Data JPA와 같은 ORM 기술을 사용할 때 적용하기 좋은 패턴이다.

 

 

Spring Boot와 JPA를 활용하여 도메인 모델 패턴을 적용한 예시를 보겠다.

 

@Entity
@Table(name = "orders")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "order_id")
    private Long id;

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

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) //OrderItem의 order 필드에 의해 변경됨
    private List<OrderItem> orderItems = new ArrayList<>();

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

    private LocalDateTime orderDate; //주문 시간

    @Enumerated(EnumType.STRING) //Enum 타입 지정: Ordinal로 하면 숫자로 지정되기 때문에 순서가 바뀌면 에러남
    private OrderStatus status; //주문 상태 [ORDER, CANCEL]

    //연관관계 편의 메서드 - 양방향일 때 사용하기 좋음//
    public void setMember(Member member) {
        this.member = member;
        member.getOrders().add(this);
    }

    public void addOrderItem(OrderItem orderItem) {
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }

    public void setDelivery(Delivery delivery) {
        this.delivery = delivery;
        delivery.setOrder(this);
    }

    //==생성 메서드==//
    public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) {
        Order order = new Order();
        order.setMember(member);
        order.setDelivery(delivery);
        for (OrderItem orderItem : orderItems) {
            order.addOrderItem(orderItem);
        }
        order.setStatus(OrderStatus.ORDER);
        order.setOrderDate(LocalDateTime.now());
        return order;
    }

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

        this.setStatus(OrderStatus.CANCEL);
        for (OrderItem orderItem : orderItems) {
            orderItem.cancel();
        }
    }

    //==조회 로직==//
    /**
     * 전체 주문가격 조회
     */
    public int getTotalPrice() {
        return orderItems.stream()
                .mapToInt(OrderItem::getTotalPrice)
                .sum();
    }

}

 

이처럼 비즈니스 로직을 Entity가 가지며 객체 지향의 특성을 적극적으로 활용하는 방식이라 볼 수 있다.

 

도메인 모델의 장점은 객체 지향에 기반한 재사용성과 유지 보수성에 있는데, 구현이 비교적 복잡할 수 있으며 초기 개발에 시간이 많이 소요될 수 있다.

 

 

 

트랜잭션 스크립트 패턴

 

트랜잭션 스크립트 패턴은 간단한 로직이나 데이터 처리를 위해 스크립트의 형태로 로직을 구현하는 방법이다.

주로 비즈니스의 로직이 간단하고 트랜잭션의 단위가 작을 때 사용하며, 데이터베이스와 직접 상호작용하여 데이터 처리를 수행한다.

 

예를 들어 은행의 계좌 이체 서비스를 생각해 보자.

계좌 이체의 로직은 '나의 잔고 확인 -> 받는 사람과 송금액 입력 -> 이체 -> 잔고 감소'가 될 것이고, 이 과정이 모두 성공해야 정상적으로 처리가 되는 것이다.

 

즉 이런 각각의 트랜잭션에 대해 개별적인 스크립트를 사용하여 데이터를 처리하는 방식이 트랜잭션 스크립트이다.

 

 

위의 코드 예시를 트랜잭션 스크립트로 적용한다면, Entity가 아니라 Service 계층에서 대부분의 비즈니스 로직을 처리하게 된다.

ORM을 사용하지 않고 직접 SQL을 작성할 때 적용하기 좋은 패턴이다.

 

구현이 간단하고 직관적이라는 장점이 있지만, 코드의 가독성이 떨어질 수 있고 복잡한 비즈니스 로직을 표현하기에는 적합하지 않다.

 

 

 

어떤 패턴을 사용해야 할까?

 

두 가지 패턴은 각각의 장단점이 있으므로 프로젝트의 특성에 맞게 유지보수가 용이한 쪽으로 생각해서 선택하는 것이 좋고,

한 프로젝트 내에서도 두 가지 패턴을 적절하게 활용할 수 있음을 알아두자.

 

 

 

 

참고

 

 

 

Contents

Copied URL!

Liked this Posting!