Search
moon
sun

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

Tags
Study
JPA
Database
Last edited time
2025/07/03 10:31
2 more properties
Search
JPA의 사실과 오해 (9) 동시성 제어 / 애그리거트 완성하기
Study
JPA
Database
JPA의 사실과 오해 (9) 동시성 제어 / 애그리거트 완성하기
Study
JPA
Database
💡
해당 포스팅은 조영호님의 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
복사