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