Search

JPA의 사실과 오해 (1) 도메인 레이어, 트랜잭션 스크립트 패턴

Tags
Study
JPA
Database
Last edited time
2025/06/22 09:21
2 more properties
해당 포스팅은 조영호님의 JPA의 사실과 오해라는 강의 내용을 바탕으로 작성되었습니다.

1. 도메인 레이어 구현 방식

도메인 레이어의 구현 방식
트랜잭션 스크랩트 패턴
도메인 모델 패턴
classDiagram
    class Process {
        +process()
    }
    class Data {
        +getter
        +setter
    }
    Process ..> Data : uses
Mermaid
복사
트랜잭션 스크립트 패턴
classDiagram
    class Object {
        +field
        +method()
    }
    class ChildObject1
    class ChildObject2

    Object <|-- ChildObject1
    Object <|-- ChildObject2
Mermaid
복사
도메인 모델 패턴

1.1. 트랜잭션 스크립트 패턴

1.1.1. 정의

프로세스와 데이터 분리
도메인 로직을 절차지향 방식으로 구현
빈약한 도메인 모델의 데이터를 이용해서 로직 처리

1.1.2. 도메인 레이어

단일 프로시저로 구성되며, 서비스 레이어와 도메인 레이어를 별도로 분리하지 않음
Fat Service

1.1.3. 퍼시스턴스 레이어

테이블 데이터 게이트웨이 (Table Data Gateway)
하나의 데이터베이스 테이블에 대한 게이트웨이 역할을 수행
단일 테이블 뷰에 접속하는 SQL 처리
데이터 접속 객체 (Data Access Object)
하나의 테이블 또는 뷰에 대한 영속성 로직을 담당하는 객체
테이블 데이터 게이트웨이 패턴의 또다른 이름
classDiagram
    class MovieDAO {
        +find(id): Movie
        +findWithTitle(title: String): Movie
        +update(movie)
        +insert(movie)
        +delete(id)
    }

    class MOVIE_DB {
        ID
        TITLE
        RUNNING_TIME
        FEE_AMOUNT
    }

    MovieDAO ..> MOVIE_DB : access DB Table
Mermaid
복사

1.2. 도메인 모델 패턴

1.2.1. 정의

프로세스와 데이터 통합
도메인 로직을 객체지향 방식으로 구현
상태와 행위를 포함하는 객체 사이의 협력을 통해 기능 구현

1.2.2. 도메인 모델

추상적으로 도메인을 이렇게 봅시다 라고 정의한 모델
classDiagram
    class Screening {
        +reserve(customer, audienceCount)
    }
    class Reservation
    class Customer
    class Movie {
        +calculateFee(screening)
    }
    class DiscountPolicy {
        +calculateDiscount(screening)
    }
    class AmountDiscountPolicy
    class PercentDiscountPolicy
    class DiscountCondition {
        <<interface>>
        +isSatisfiedBy(screening)
    }
    class SequenceCondition
    class PeriodCondition

    Screening "1" --> "*" Reservation : create
    Reservation "*" --> "1" Customer
    Screening "*" --> "1" Movie
    Movie "1" --> "0..1" DiscountPolicy
    DiscountPolicy <|-- AmountDiscountPolicy
    DiscountPolicy <|-- PercentDiscountPolicy
    DiscountPolicy "1" --> "1..*" DiscountCondition
    DiscountCondition <|.. SequenceCondition
    DiscountCondition <|.. PeriodCondition
Mermaid
복사

1.2.3. 서비스 레이어

어플리케이션 경계
어플리케이션 로직
도메인 로직의 재사용성 촉진

1.2.4. 도메인 레이어

객체지향으로 작성되어야할 도메인 로직을 담당

1.2.5. 객체 관계 임피던스 불일치 (Object-Relational Impedance Mismatch)

객체 모델과 DB 스키마 사이의 불일치
객체 패러다임과 관계 패러다임 간의 불일치
테이블보다 더많은 클래스를 뽑음
복잡함 → 맵핑 필요
classDiagram
    %% Object Model
    class DiscountStrategy
    class AmountStrategy
    class PercentStrategy
    class NonDiscountStrategy
    class Rule
    class SequenceRule
    class TimeOfDayRule

    DiscountStrategy <|-- AmountStrategy
    DiscountStrategy <|-- PercentStrategy
    DiscountStrategy <|-- NonDiscountStrategy
    DiscountStrategy --> Rule
    Rule <|-- SequenceRule
    Rule <|-- TimeOfDayRule

    %% Relational Model
    class DISCOUNT {
        MOVIE_ID (FK)
        DISCOUNT_TYPE
        FEE_AMOUNT
        FEE_CURRENCY
        PERCENT
    }
    class RULE {
        ID
        DISCOUNT_ID (FK)
        POSITION
        RULE_TYPE
        DAY_OF_WEEK
        START_TIME
        END_TIME
        SEQUENCE
    }
    DISCOUNT "1" <-- "0..*" RULE : contains
Mermaid
복사

1.2.6. 데이터 매퍼 (Data Mapper)

객체 모델과 DB 스키마 간의 의존성 끊고 중간에서 관리
객체 모델과 DB 스키마 간의 독립성 유지

1.2.7. 객체 관계 맵핑 (Object Relation Mapping, ORM)

객체와 데이터베이스 테이블 사이의 맵핑 기법 또는 맵핑 도구
일반적으로 재사용 가능한 데이터 맵핑 프레임워크 사용

1.2.8. 리파지토리 (Repository)

데이터 매퍼 용도로 사용되는 객체
일반적으로 데이터 접근 객체의 개념을 포함

1.3. JPA

자바에서 사용되는 ORM
범용적인 기능을 제공함 → 어떻게 사용하냐에 따라 복잡도나 난이도가 높아짐

1.3.1. 트랜잭션 스크립트 패턴과 JPA

SQL 자동화의 목적으로 단순하게 쓰는 것이 권장됨
Lazy Loading, 연관관계 맵핑 등 복잡한 기술을 쓰는 순간 복잡도가 어려워짐
따라서 쿼리 자동 생성 목적으로 JPA를 사용할 것
퍼시스턴스 레이어가 Table Data Gateway(DAO) 역할만 수행
classDiagram
    %% 도메인 레이어
    class Process {
        +process()
    }
    class Data {
        +getter()
        +setter()
        id
        title
        runningTime
        feeAmount
    }

    %% 관계
    Process --> Data : use
Mermaid
복사
트랜잭션 스크립트 패턴의 도메인 레이어
classDiagram
    %% 퍼시스턴스 레이어
    class MovieDAO {
        +find(id): Movie
        +findWithTitle(title: String): Movie
    }
    class MOVIE {
        ID
        TITLE
        RUNNING_TIME
        FEE_AMOUNT
    }

    %% 관계
    MovieDAO ..> MOVIE : 비슷한 형태의 객체테이블 변환. \n SQL 자동화
Mermaid
복사
트랜잭션 스크립트 패턴의 영속성 레이어

1.3.2. 도메인 모델 패턴과 JPA

객체 지향 / 도메인 모델 패턴을 쓴다면 임피던스 불일치를 해결하기 위해 사용
결국 JPA 라는 ORM을 통해 임피던스 불일치를 해결하겠다는 것
classDiagram
    class DiscountStrategy
    class AmountStrategy
    class PercentStrategy
    class NonDiscountStrategy
    class Rule
    class SequenceRule
    class TimeOfDayRule

    DiscountStrategy <|-- AmountStrategy
    DiscountStrategy <|-- PercentStrategy
    DiscountStrategy <|-- NonDiscountStrategy
    DiscountStrategy --> Rule
    Rule <|-- SequenceRule
    Rule <|-- TimeOfDayRule

    %% === DB 테이블 ===
    class DISCOUNT {
        MOVIE_ID (FK)
        DISCOUNT_TYPE
        FEE_AMOUNT
        FEE_CURRENCY
        PERCENT
    }
    class RULE {
        ID
        DISCOUNT_ID (FK)
        POSITION
        RULE_TYPE
        DAY_OF_WEEK
        START_TIME
        END_TIME
        SEQUENCE
    }
    DISCOUNT "1" <-- "0..*" RULE : contains
    DiscountStrategy  <.. DISCOUNT  : JPA로 임피던스 불일치 해결

Mermaid
복사

2. 트랜잭션 스크립트 패턴 예시 (영화 예매 시스템)

2.1. 시스템 요구사항/도메인 분석

2.1.1. 도메인 개념

영화 (Movie)
기본 정보 (공통의 메타 정보)
상영 (Screening)
실제로 시스템에서 관객들이 구매하는 상품
예) 2025.04.19 09:30
할인 정책
금액 할인 정책
1천원 할인
비율 할인 정책
10% 할인
할인 조건
순서 조건
조조 상영인 경우
10회 상영인 경우
기간 조건
월요일 10:00 ~ 12:00 상영인 경우
목요일 18:00 ~ 21:00 상영인 경우
예매
제목, 상영시간, 인원
정가, 결제 금액등

2.1.2. 도메인 개념 요약

classDiagram
    class Screening {
        <<상영>>
    }
    class Reservation {
        <<예매>>
    }
    class Movie {
        <<영화>>
    }
    class DiscountPolicy {
        <<할인 정책>>
    }
    class DiscountCondition {
        <<할인 조건>>
    }

    Screening "1" --> "0..*" Reservation
    Screening "0..*" --> "1" Movie
    Movie "1" --> "0..1" DiscountPolicy
    DiscountPolicy "1" --> "1..*" DiscountCondition
Mermaid
복사

2.2. 설계 및 구현

classDiagram
    class Process {
        +process()
    }
    class Data {
        +getter
        +setter
    }
    Process ..> Data : uses
Mermaid
복사

2.2.1. 데이터 (객체)

무엇을 저장할 것인가?에 대한 부분
도메인 개념을 저장할 각각의 빈약한 도메인 모델들이 도출됨
구현
메세드를 통해 내부 캡슐화 진행
필드는 클래스 내부에 캡슐화
어떠한 상황에서도 제한없이 접근가능하도록 모든 필드에 getter / setter 추가
@NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Getter @Setter public class Movie { private Long id; private Long policyId; private String title; private Integer runningTime; private Long fee; public Movie(Long policyId, String title, Integer runningTime, Long fee) { this.policyId = policyId; this.title = title; this.runningTime = runningTime; this.fee = fee; } }
Java
복사

2.2.2. 프로세스 (알고리즘, 도메인 로직)

어떻게 처리할 것인가에 대한 부분
DAO → 영속성 저장소와의 상호작용을 담당하는 객체
프로세스 정의
Service 클래스 안에 프로세스를 구현하기 위한 메서드 정의
이후 절차적으로 구현 진행
package org.eternity.script.movie.service; import jakarta.transaction.Transactional; import org.eternity.script.movie.domain.*; import org.eternity.script.movie.persistence.*; import org.springframework.stereotype.Service; import java.util.List; @Service public class ReservationService { private ScreeningDAO screeningDAO; private MovieDAO movieDAO; private DiscountPolicyDAO discountPolicyDAO; private DiscountConditionDAO discountConditionDAO; private ReservationDAO reservationDAO; public ReservationService(ScreeningDAO screeningDAO, MovieDAO movieDAO, DiscountPolicyDAO discountPolicyDAO, DiscountConditionDAO discountConditionDAO, ReservationDAO reservationDAO) { this.screeningDAO = screeningDAO; this.movieDAO = movieDAO; this.discountConditionDAO = discountConditionDAO; this.discountPolicyDAO = discountPolicyDAO; this.reservationDAO = reservationDAO; } @Transactional public Reservation reserveScreening(Long customerId, Long screeningId, Integer audienceCount) { // 1. 데이터베이스 조회 Screening screening = screeningDAO.selectScreening(screeningId); Movie movie = movieDAO.selectMovie(screening.getMovieId()); DiscountPolicy policy = discountPolicyDAO.selectDiscountPolicy(movie.getId()); List<DiscountCondition> conditions = discountConditionDAO.selectDiscountConditions(policy.getId()); // 2. 할인 여부 판단 DiscountCondition condition = findDiscountCondition(screening, conditions); // 3. 할인 요금 계산 Long fee; if (condition != null) { fee = movie.getFee() - calculateDiscount(policy, movie); } else { fee = movie.getFee(); } // 4. 예매 생성 Reservation reservation = makeReservation(customerId, screeningId, audienceCount, fee); reservationDAO.insert(reservation); return reservation; } }
Java
복사

2.3. 요약

중앙 집중식 제어 스타일
sequenceDiagram
    participant RS as :ReservationService
    participant SDAO as :ScreeningDAO
    participant DCDAO as :DiscountConditionDAO
    participant DPDAO as :DiscountPolicyDAO
    participant DC as :DiscountCondition
    participant S as :Screening
    participant DP as :DiscountPolicy
    participant R as :Reservation

    RS->>RS: reserveScreening()
    RS->>SDAO: selectScreening()
    SDAO-->>RS: 
    RS->>DCDAO: selectDiscountConditions()
    DCDAO-->>RS: 
    RS->>DPDAO: selectDiscountPolicy()
    DPDAO-->>RS: 
    RS->>DC: isPeriodCondition()
    DCDAO-->>RS: 
    RS->>S: playedIn()
    SDAO-->>RS: 
    RS->>DC: isAmountCondition()
    DCDAO-->>RS: 
    RS->>DP: getAmount()
    DPDAO-->>RS: 
    RS->>DC: isPercentCondition()
    DCDAO-->>RS: 
    RS->>DP: getPercent()
    DPDAO-->>RS: 
    RS->>R: new
Mermaid
복사
낮은 응집도
단일 책임 원칙이 지켜지지 않음
빈약한 모델의 내부 필드가 변경될 이유가 너무 많음
높은 결합도
DB, 도메인 로직 등과 강하게 결합되어 있음 (읽고 처리하고 등등..)
아키텍처 예시
classDiagram
    class ReservationController {
        +reserveScreening()
    }
    class ReservationService {
        +reserveScreening()
    }
    class MovieDAO {
        <<interface>>
        +selectMovie(movieId)
        +insert(movie)
    }
    class ScreeningDAO {
        <<interface>>
        +selectScreening(screeningId)
        +insert(screening)
    }
    class DiscountPolicyDAO {
        <<interface>>
        +selectDiscountPolicy(movieId)
        +insert(discountPolicy)
    }
    class DiscountPolicy {
        id
        movieId
        policyType
        amount
        percent
        +getter/setter
    }

    ReservationController --> ReservationService
    ReservationService --> MovieDAO
    ReservationService --> ScreeningDAO
    ReservationService --> DiscountPolicyDAO
    MovieDAO ..> DiscountPolicy
    ScreeningDAO ..> DiscountPolicy
    DiscountPolicyDAO ..> DiscountPolicy
Mermaid
복사