[디자인패턴] 트랜잭션 스크립트 패턴과 도메인 모델 패턴

2025. 5. 17. 12:01·CS
728x90

JPA 강의를 들으면서 도메인 모델 패턴을 접하게 되었다. 이전까지는 모든 비즈니스 로직은 서비스에서 처리하고, 엔티티에서는 생성자와 getter setter 정도만 정의하는 트랙잭션 스크립트 패턴을 사용했기 때문에 어떤 게 더 좋은지(?)에 대한 지식 충돌이 생겼다. 

 

트랜잭션 스크립트 패턴

트랜잭션 스크립트란? 트랜잭션 스크립트는 우리가 흔히 은행에서 이체를 할 때 '내 계좌에서 잔고 확인 -> 받는 사람 확인 -> 이체 실행 -> 잔고 감소' 순으로 진행되는 로직이 하나의 로직으로 처리되어야 하고, 이 과정에서 한 번이라도 오류가 발생하면 트랙잭션의 원자성(All or Nothing)에 의해 모든 과정이 취소되어야 한다. (만약 오류가 발생하지 않으면 모든 과정이 처리되어야 한다.)

 

즉, 트랜잭션 스크립트는 비즈니스 로직을 메서드 단위의 절차형 스크립트로 구현하는 패턴을 말한다. 각 메서드(혹은 서비스 함수)가 하나의 유스케이스(예: 회원 가입, 상품 주문 등)를 처리하며, 해당 로직이 명령문처럼 위에서 아래로 흘러가는 구조이다. 

트랜잭션 스크립트에서의 비즈니스 로직 = 서비스 계층에서 직접 처리하는 코드 

 

예를들어, 사용자가 상품을 주문한다면

public class OrderService {

    @Transactional
    public Long createOrder(Long memberId, Long itemId, int quantity) {
        // 1. 사용자 및 상품 조회
        Member member = memberRepository.findById(memberId);
        Item item = itemRepository.findById(itemId);

        // 2. 배송 생성
        Delivery delivery = new Delivery();
        delivery.setAddress(member.getAddress());

        // 3. 주문 아이템 생성
        OrderItem orderItem = new OrderItem();
        orderItem.setItem(item);
        orderItem.setOrderPrice(item.getPrice());
        orderItem.setCount(quantity);

        // 4. 주문 생성
        Order order = new Order();
        order.setMember(member);
        order.setDelivery(delivery);
        order.addOrderItem(orderItem);
        order.setStatus(OrderStatus.ORDERED);

        // 5. 저장
        orderRepository.save(order);

        return order.getId();
    }
}

사용자 및 상품을 조회하고 → 배송을 생성하고 → 주문 아이템을 생성하고 → 주문을 생성한 뒤, → 저장한다.

 

이 방식은 굉장히 간단하고 직관적이지만 로직이 중복될 수 있다.

public class OrderService {

    @Transactional
    public Long createOrderFromCart(Long memberId, List<Long> cartItemIds) {
        // 1. 사용자 조회
        Member member = memberRepository.findById(memberId);

        // 2. 배송 생성
        Delivery delivery = new Delivery();
        delivery.setAddress(member.getAddress());

        // 3. 장바구니 아이템 조회 및 주문 아이템 생성
        List<OrderItem> orderItems = new ArrayList<>();
        for (Long cartItemId : cartItemIds) {
            CartItem cartItem = cartItemRepository.findById(cartItemId);
            Item item = cartItem.getItem();

            OrderItem orderItem = new OrderItem();
            orderItem.setItem(item);
            orderItem.setOrderPrice(item.getPrice());
            orderItem.setCount(cartItem.getQuantity());

            orderItems.add(orderItem);
        }

        // 4. 주문 생성
        Order order = new Order();
        order.setMember(member);
        order.setDelivery(delivery);
        for (OrderItem orderItem : orderItems) {
            order.addOrderItem(orderItem);
        }
        order.setStatus(OrderStatus.ORDERED);

        // 5. 저장
        orderRepository.save(order);

        return order.getId();
    }
}

사용자 조회 → 배송 생성 → 주문 아이템 만들기 → 주문 객체 조립 → 저장. 어디서 많이 본 구조의 코드인데?

한번 자세히 봐보자. createOrder 와 createOrderFromCart의 중복 코드는 아래와 같다.

 

1. 사용자 주소를 기반으로 Delivery 객체 생성

Delivery delivery = new Delivery();
delivery.setAddress(member.getAddress());

 

2. 주문 아이템 생성

OrderItem orderItem = new OrderItem();
orderItem.setItem(item);
orderItem.setOrderPrice(item.getPrice());
orderItem.setCount(quantity);

OrderItem orderItem = new OrderItem();
orderItem.setItem(item);
orderItem.setOrderPrice(item.getPrice());
orderItem.setCount(cartItem.getQuantity());

 

3. 객체 생성 및 조립

Order order = new Order();
order.setMember(member);
order.setDelivery(delivery);
order.addOrderItem(orderItem);
order.setStatus(OrderStatus.ORDERED);

Order order = new Order();
order.setMember(member);
order.setDelivery(delivery);
for (OrderItem orderItem : orderItems) {
    order.addOrderItem(orderItem);
}
order.setStatus(OrderStatus.ORDERED);

 

아마 JSP를 배운 사람이라면 트랜잭션 스크립트가 더 익숙할 거다. 특히나 구현 방식이 단순하기 때문에 쉽게 코드를 구현할 수 있다는 장점이 있다. 

하지만, 트랜잭션 스크립트를 사용하게 되면 쉬운 개발에 익숙해지기 때문에 공통된 코드를 공통 모듈로 분리하지 않고 복사&붙이기 방식으로 중복된 코드를 만드는 유혹에 빠지기 쉽다. 또한 조건이나 처리 방식이 바뀌면 양쪽 다 수정해야 한다..

 

도메인 모델 패턴

도메인 모델 패턴은 애플리케이션의 비즈니스 로직을 도메인 객체(엔티티) 내부에 포함시키는 설계 패턴입니다. 즉, 비즈니스 규칙, 데이터 변경, 데이터 검증 등을 도메인 객체가 직접 수행하도록 설계한다. 이 패턴은 DDD(Domain-Driven Design)의 핵심 개념 중 하나로 객체지향의 장점을 최대한 활용하는 모델 패턴이다.

 

비즈니스 로직을 엔티티에 넣는다는 말은 예를들어,

// Order.java (도메인 객체)
public class Order {
    private List<OrderItem> items;
    private Member member;
    private Delivery delivery;

    public static Order createOrder(Member member, Delivery delivery, OrderItem... items) {
        Order order = new Order();
        order.setMember(member);
        order.setDelivery(delivery);
        for (OrderItem item : items) {
            order.addOrderItem(item);
        }
        order.setStatus(OrderStatus.ORDERED);
        return order;
    }

    public void cancel() {
        if (delivery.isShipped()) throw new IllegalStateException("이미 배송된 주문은 취소 불가");
        this.setStatus(OrderStatus.CANCELLED);
        for (OrderItem item : items) {
            item.cancel();
        }
    }
}

이렇게 엔티티 도메인 내에서 핵심 비즈니스 로직이 동작하는 것을 말한다.

 

도메인 모델로 로직을 작성하면 역시나 객체지향에 기반한 재사용성, 확장성, 유지보수가 편리해진다는 장점이 있다.
특히, 객체지향적으로 코드를 구현할 수 있다는 점이 가장 큰 장점이다.
엔티티에 비즈니스 로직을 작성하고, 서비스 계층에서는 각 도메인 객체가 수행할 책임을 위임받아 조율만 하게 되기 때문에 코드가 명확하고 중복 없이 깔끔하게 구성된다.

다만, 도메인 모델 패턴은 하나의 도메인 모델을 정교하게 구축하는 데에 꽤 많은 시간과 고민이 필요하다!
객체 간 책임을 명확히 나누고, 비즈니스 규칙을 적절히 분산시키기 위한 설계 역량도 요구되기 때문에 작은 프로젝트보다는, 복잡한 도메인이나 장기적으로 유지보수가 필요한 시스템에 더욱 적합하다.

 

  트랜잭션 스크립트 패턴 도메인 모델 패턴
로직 위치 서비스 계층에 집중 도메인 객체 내부
개발 방식 절차지향적 (순서대로 처리) 객체지향적 (객체 책임 중심)
코드 중복 많아지기 쉬움 적음 (응집도 높음)
유지보수 구조 커질수록 어려움 구조 커질수록 유리
초기 진입장벽 낮음 비교적 높음

 

어떤 게 더 좋다, 나쁘다고 말할 수는 없다. 어떨 때는 트랜잭션 스크립트 패턴이 더 적합할 수 있고, 어떨 때는 도메인 모델 패턴이 더 적합할 수 있다. 또한 하나의 프로젝트 내부에서도 트랙잭션 스크립트 패턴과 도메인 모델 패턴을 혼용할 수도 있다!
728x90

'CS' 카테고리의 다른 글

[CS] 메시지 지향 미들웨어(MOM)  (1) 2024.12.02
[CS] 기능적 요구사항 vs 비기능적 요구사항  (0) 2024.12.02
[CS] 플랫폼  (0) 2024.12.02
[CS] SOLID 객체지향 프로그래밍의 5가지 원칙  (3) 2024.11.28
[Spring] Spring Boot vs Spring  (1) 2024.11.21
'CS' 카테고리의 다른 글
  • [CS] 메시지 지향 미들웨어(MOM)
  • [CS] 기능적 요구사항 vs 비기능적 요구사항
  • [CS] 플랫폼
  • [CS] SOLID 객체지향 프로그래밍의 5가지 원칙
DROPDEW
DROPDEW
💻 Developer | 기록하지 않으면 존재하지 않는다
  • DROPDEW
    제 2장 1막
    DROPDEW
  • 전체
    오늘
    어제
    • Dev (443)
      • App·Android (1)
      • BE (50)
        • HTTP 웹 기본 지식 (8)
        • 스프링 입문 - 코드로 배우는 스프링 부트, 웹 .. (12)
        • 스프링부트와 JPA 활용 (11)
        • 스프링부트 시큐리티 & JWT (0)
        • 실전 자바 기본, 중급 (1)
        • PHP (11)
      • FE·Client (23)
        • HTML (1)
        • React (19)
        • Unity (1)
      • Data (28)
        • AI (7)
        • Bigdata (6)
        • Database (1)
        • Python (0)
        • 빅데이터분석기사 (13)
      • Infra (1)
      • Activity (9)
        • Intern (0)
        • SK AI Dream Camp (2)
        • 구름톤 유니브 4기 (1)
        • 리모트 인턴십 6기 (3)
        • 봉사활동 (0)
        • 부스트캠프 AI Tech 8기 (3)
      • CS (8)
      • 취준 (12)
        • 자격증 (4)
        • 인적성·NCS (6)
        • 코테·필기·면접 후기 (2)
      • 코테 (270)
        • Algorithm (222)
        • SQL (35)
        • 정리 (13)
      • 인사이트 (27)
        • 금융경제뉴스 (7)
        • 금융용어·지식 (2)
        • 북마크 (7)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    누적합
    시뮬레이션
    오블완
    브루트포스 알고리즘
    투포인터
    그래프탐색
    문자열
    매개변수탐색
    백준
    그래프이론
    다이나믹프로그래밍
    그리디알고리즘
    자료구조
    티스토리챌린지
    최단경로
    구현
    수학
    이분탐색
    정렬
    너비우선탐색
  • 최근 댓글

  • 최근 글

  • 250x250
  • hELLO· Designed By정상우.v4.10.3
DROPDEW
[디자인패턴] 트랜잭션 스크립트 패턴과 도메인 모델 패턴
상단으로

티스토리툴바