Skip to main content

트랜잭션 스크립트 패턴, 도메인 모델 패턴, ORM

•4 min•679 words

도메인 모델 패턴 vs 트랜잭션 스크립트 패턴

  • 소프트웨어 개발, 특히 비즈니스 로직을 처리할 때 자주 사용되는 두 가지 기본 패턴이다.

📜 트랜잭션 스크립트 패턴 (Transaction Script Pattern)

하나의 비즈니스 트랜잭션(요청)을 처리하기 위한 단일 프로시저(스크립트)를 만드는 방식

예시

  • 온라인 쇼핑몰에서 ‘상품 주문’이라는 기능을 구현할 때, 트랜잭션 스크립트 패턴은 다음과 같은 하나의 함수로 모든 것을 처리한다.
function placeOrder(userId, itemId, quantity) {
  // 1. 사용자 정보 확인
  // 2. 상품 재고 확인
  // 3. 가격 계산
  // 4. 주문 정보 데이터베이스에 저장
  // 5. 상품 재고 감소
  // 6. 사용자에게 이메일 발송
}

주요 특징

  • 절차적이고 간단하다.
  • 데이터 중심적이다.
  • 데이터베이스의 구조와 강하게 결합되는 경우가 많다.
  • 비즈니스 로직이 주로 데이터를 읽고 쓰는 과정으로 표현된다.

장점

  • 간단한 애플리케이션의 경우 빠르게 기능을 구현할 수 있다.
  • 복잡한 객체지향 개념 없이도 쉽게 이해하고 적용할 수 있다.

단점

  • 여러 트랜잭션에서 동일한 로직이 반복적으로 나타날 가능성이 높다.
  • 예를 들어, ‘사용자 정보 확인’ 로직이 여러 다른 기능에 중복해서 들어갈 수 있다.
  • 비즈니스 로직이 복잡해질수록 스크립트가 길고 복잡해져 유지보수가 어려워진다.
  • 로직의 파편화로 인해 전체 시스템을 이해하기 힘들어진다.

🧠 도메인 모델 패턴 (Domain Model Pattern)

비즈니스 로직과 데이터를 객체로 묶어 표현하는 방식

예시

  • ‘상품 주문’ 기능을 도메인 모델 패턴으로 구현하면, Order, Product, Customer 같은 객체들이 각자의 책임을 가지고 서로 협력한다.
class Order {
  // 주문 관련 데이터 (주문자, 상품 목록 등)

  void place() { // 주문 처리 로직
    customer.verify(); // 고객 유효성 검사
    for (item in orderItems) {
      product.decreaseStock(item.quantity); // 상품 재고 감소
    }
    // 주문 상태 변경, 결제 시스템 호출 등
  }
}

class Product {
  // 상품 관련 데이터 (재고, 가격 등)

  void decreaseStock(int quantity) { // 재고 감소 로직
    if (this.stock < quantity) {
      throw new Exception("재고 부족");
    }
    this.stock -= quantity;
  }
}

주요 특징

  • 데이터와 행위를 캡슐화한 객체들이 서로 상호작용하며 비즈니스 로직을 처리한다.
  • 도메인 객체들은 스스로의 상태를 변경하고 비즈니스 규칙을 검증하는 등 필요한 로직을 내부에 포함한다.
  • 각 객체는 자신의 책임에만 집중하므로(높은 응집도), 다른 객체와의 의존성이 낮아져(낮은 결합도) 시스템이 유연해진다.

장점

  • 공통 로직이 해당 책임을 가진 객체 안에 캡슐화되어 재사용성이 높아지고 중복이 줄어든다.
  • 비즈니스 규칙이 변경될 때 관련된 객체만 수정하면 되므로 유지보수가 쉽다.
  • 실제 비즈니스 개념과 코드의 구조가 일치하여 복잡한 도메인을 이해하기 쉽다.

단점

  • 객체지향 설계 원칙에 대한 이해가 필요하고, 초기 설계에 많은 노력이 든다.
  • 트랜잭션 스크립트 패턴에 비해 배우고 숙달하는 데 시간이 걸린다.

언제 무엇을 선택해야 할까?

두 패턴은 ‘정답’이 있는 것이 아니라 상황에 맞는 ‘도구’이다.

기준트랜잭션 스크립트 패턴도메인 모델 패턴
적합한 경우간단한 CRUD 중심의 애플리케이션, 비즈니스 로직이 복잡하지 않은 경우비즈니스 로직이 복잡하고, 장기적으로 확장 및 유지보수가 중요한 경우
복잡도낮음높음
코드 구조절차적, 데이터 중심객체지향적, 행위 중심
유지보수성낮음 (복잡해질수록)높음
개발 속도(초기)빠름느림

ORM (Object-Relational Mapping)

‘도메인 모델 패턴’을 구현할 때 발생하는 데이터베이스 작업을 자동화하고, 개발자가 비즈니스 로직에만 집중할 수 있도록 도와주는 핵심적인 다리(Bridge) 역할

🤔 무엇이 문제였을까?

  • 도메인 모델 패턴의 핵심은 Order, Product, User와 같은 객체들이 데이터와 행위(비즈니스 로직)를 함께 가지는 것이다.
  • 개발자는 이 객체들을 가지고 비즈니스 로직을 멋지게 구현했다.
  • 하지만 여기서 큰 질문이 생긴다.

    “메모리 상에서 존재하는 이 객체들을 어떻게 데이터베이스에 저장하고, 또 어떻게 데이터베이스에서 다시 객체 형태로 불러올 것인가?”

  • 이것이 바로 객체-관계 불일치 문제이다.
  • 객체는 상속, 다형성, 복잡한 연관 관계 등을 가지지만, 관계형 데이터베이스는 단순한 테이블 구조를 가진다.
  • 이 둘의 패러다임이 다르기 때문에 변환 과정이 매우 번거롭다.

🥲 ORM이 없었다면?

  • ORM이 없다면, 개발자는 다음과 같은 지루한 작업을 직접 해야 한다.
  • 객체 → DB 저장 시: Order 객체의 필드 값을 하나씩 꺼내서 INSERT INTO orders ... SQL 문을 직접 작성해야 한다.
  • DB → 객체 조회 시: SELECT * FROM orders ... SQL 문의 결과를 한 줄씩 읽어서 Order 객체를 직접 생성하고 필드 값을 채워 넣어야 한다.
  • 이런 코드는 반복적이고, 실수하기 쉬우며, 비즈니스 로직과 데이터 접근 로직이 뒤섞여 코드를 복잡하게 만든다.

도메인 모델 패턴의 든든한 조력자 ORM

  • ORM은 위에서 언급한 지루하고 반복적인 작업을 완전히 자동화해 준다.
  • 개발자는 더 이상 SQL을 직접 작성할 필요 없이, 마치 객체 컬렉션을 다루듯이 데이터베이스를 다룰 수 있게 된다.

예시

ORM 없을 때 (직접 SQL 작성)

// order 객체의 정보를 DB에 저장하려면...
String sql = "INSERT INTO orders (order_id, member_id, order_date) VALUES (?, ?, ?)";
// ... JDBC 코드로 sql 실행 ...

ORM 있을 때 (JPA 같은 기술)

orderRepository.save(order);
  • ORM이 내부적으로 order 객체를 분석해서 적절한 INSERT SQL을 생성하고 실행해 준다.
  • 개발자는 save라는 메서드만 알면 된다.

트랜잭션 스크립트 패턴은 ORM을 사용하지 않나?

  • 반면 트랜잭션 스크립트 패턴은 애초에 절차적이며, 데이터베이스 중심적으로 로직을 작성하는 경우가 많다.
  • 하나의 스크립트 안에서 필요한 데이터를 조회하고(SELECT), 비즈니스 로직을 처리한 뒤, 결과를 업데이트(UPDATE)하는 흐름이 명확하다.
  • 이런 구조에서는 SQL을 직접 작성하는 것이 오히려 더 직관적이고 자연스러울 수 있다.
  • 물론 트랜잭션 스크립트 패턴에서도 ORM을 사용하여 데이터 조회나 저장을 단순화할 수는 있지만, 도메인 모델 패턴만큼 ORM의 강력한 기능(객체 그래프 탐색, 변경 감지 등)을 온전히 활용하지는 못한다.

참고 자료