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
복사