2025년 10월 16일

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

도메인 모델 패턴 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의 강력한 기능(객체 그래프 탐색, 변경 감지 등)을 온전히 활용하지는 못한다.
 

참고 자료