Rich Domain Model.pdf

open.egovframe.go.kr

Rich Domain Model.pdf

Object-Oriented Design & Architecture

Rich Domain Model

조영호

Eternity’s Chit-Chat

http://aeternum.egloos.com


목차

1. 영화 예매 시스템 도메인

2. 데이터-지향 설계

3. 책임-주도 설계

4. 아키텍처 & 프레임워크

5. 결롞


1. 영화 예매 시스템 도메인


온라인 영화 예매 시스템

4 / 문서의 제목


Domain Concept - 영화

Movie

5 / 문서의 제목


Domain Concept - 상영

Showing

2010-10-20 09:30 조조

2010-10-21 20:30 5회

2010-12-01 14:20 4회

6 / 문서의 제목


Domain Concept – 할인 정책

Discount

Amount Discount

\8,000 - \800 = \7,200

Percent Discount

\8,000 – (\8,000 * 0.1) = \7,200

7 / 문서의 제목


Domain Concept – 상영 할인 규칙

Rule

Sequence Rule

조조 상영인 경우

10회 상영인 경우

Time Rule

월요일 10:00 ~ 12:00 상영인 경우

목요일 18:00 ~ 21:00 상영인 경우

8 / 문서의 제목


Domain Concept –할인 정책 + 할인 규칙

Movie Discount Rule

1 0..1 1 1..*

9 / 문서의 제목


Domain Concept –할인 정책 + 할인 규칙

조조 상영인 경우

이끼

8000원

Amount DC

800원

10회 상영인 경우

월요일 10:00 ~ 12:00 상영인 경우

목요일 18:00 ~ 21:00 상영인 경우

Movie Discount Rule

1 0..1 1 1..*

10 / 문서의 제목


Domain Concept –할인 적용

상영정보

2010년 12월 21일 목요일

18:00 ~ 20:00(7회차)

조조 상영인 경우

이끼

8000원

Amount DC

800원

10회 상영인 경우

월요일 10:00 ~ 12:00 상영인 경우

목요일 18:00 ~ 21:00 상영인 경우

11 / 문서의 제목


Domain Concept –할인 적용

상영정보

2010년 12월 21일 목요일

18:00 ~ 20:00(7회차)

\7,200

조조 상영인 경우

이끼

8000원

Amount DC

800원

10회 상영인 경우

월요일 10:00 ~ 12:00 상영인 경우

목요일 18:00 ~ 21:00 상영인 경우

12 / 문서의 제목


Domain Concept –예매

Reservation



이끼

상영 정보

2010년 12월 21일 (목)

7회 6:00(오후) – 8:00(오후)



2명



16,000원

결재 금액

14,400원

13 / 문서의 제목


2. 데이터-지향 설계


무엇을 저장할 것인가 - 데이터

15 / 문서의 제목


데이터 모델

RESERVATION

CUSTOMER

ID

ID

CUSTOMER_ID(FK)

SHOWING_ID(FK)

FEE_AMOUNT

FEE_CURRENCY

AUDIENCE_COUNT

CUSTOMER_ID

NAME

SHOWING

ID

MOVIE_ID(FK)

SEQUENCE

SHOWING_TIME

MOVIE

ID

TITLE

RUNNING_TIME

FEE_AMOUNT

FEE_CURRENCY

DISCOUNT

MOVIE_ID(FK)

DISCOUNT_TYPE

FEE_AMOUNT

FEE_CURRENCY

PERCENT

RULE

ID

DISCOUNT_ID(FK)

POSITION

RULE_TYPE

DAY_OF_WEEK

START_TIME

END_TUME

SEQUENCE

16 / 문서의 제목


또 다른 데이터 표현 – Anemic Domain Model

Reservation

Customer

id

customerId

showingId

Amounr

audienceCount

Id

customerId

name

Showing

id

movieId

sequence

showingTime

Movie

id

title

runningTime

fee

Discount

movieId

discountType

amount

percent

Rule

id

discountId

position

ruleType

dayOfWeek

startTime

endTime

sequence

17 / 문서의 제목


초기 데이터

MOVIE

ID TITLE RUNNING_TIME FEE_AMOUNT FEE_CURRENCY

1 이끼 120 8000 KRW

DISCOUNT

MOVIE_ID DISCOUNTYPE FEE_AMOUNT FEE_ACURRENCY PERCENT

1 A 800 KRW NULL

RULE

ID DISCOUNT_ID POSITION RULE_TYPE DAY_OF_WEEK

START_TIME

END_TIME

SEQUENCE

1 1 0 S NULL

NULL

NULL

1

2 1 1 S NULL NULL NULL 10

3 1 2 T 2 10:00 12:00 NULL

4 1 3 T 5 18:00 21:00 NULL

SHOWING

ID MOVIE_ID SEQUENCE SHOWING_TIME

1 1 7 2010-12-21 18:00

18 / 문서의 제목


어떻게 처리할 것인가 - 프로세스

19 / 문서의 제목


예매 처리 Service


MovieDAO


ReservationService

reserveShowing(customerId, showingId, audienceCount)


DiscountDAO


RuleDAO

ReservationServiceImpl

reserveShowing(customerId, showingId, audienceCount)


ShowingDAO


ReservationDAO

20 / 문서의 제목


데이터를 사용한 예매 프로세스 구현

@Override

public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {

1 데이터베이스로부터 Movie와 Showing 정보 로딩

2 데이터베이스로부터 Rule 정보 로딩 후 Showig에 적용할 수 있는 Rule 이 있는지 판단

3 if (Rule 이 존재하면) {

Discount를 읽어 요금 할인된 요금 계산

} else {

Movie에 저장되어 있는 정액 요금 사용

}

}

4 Reservation 생성 후 데이터베이스 저장

Process-Oriented Implementation

21 / 문서의 제목


데이터를 사용한 예매 프로세스 구현

@Override

public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {

1 데이터베이스로부터 Movie와 Showing 정보 로딩

Showing showing = showingDAO.selectShowing(showingId);

Movie movie = movieDAO.selectMovie(showing.getMovieId());

2 데이터베이스로부터 Rule 정보 로딩 후 Showig에 적용할 수 있는 Rule 이 있는지 판단

3 if (Rule 이 존재하면) {

Discount를 읽어 요금 할인된 요금 계산

} else {

Movie에 저장되어 있는 정액 요금 사용

}

}

4 Reservation 생성 후 데이터베이스 저장

22 / 문서의 제목


데이터를 사용한 예매 프로세스 구현

@Override

public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {

Showing showing = showingDAO.selectShowing(showingId);

Movie movie = movieDAO.selectMovie(showing.getMovieId());

2 데이터베이스로부터 Rule 정보 로딩 후 Showig에 적용할 수 있는 Rule 이 있는지 판단

Rule rule = findRule(showing, movie);

}

3 if (Rule 이 존재하면) {

Discount를 읽어 요금 할인된 요금 계산

} else {

Movie에 저장되어 있는 정액 요금

private

사용

Rule findRule(Showing showing, Movie movie) {

}

for(Rule each : ruleDAO.selectRules(movie.getId())) {

if (each.isAccepted(showing, movie)) {

4 Reservation 생성 후 데이터베이스 저장

return each;

}

}

}

return null;

23 / 문서의 제목


데이터를 사용한 예매 프로세스 구현

@Override

public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {

Showing showing = showingDAO.selectShowing(showingId);

Movie movie = movieDAO.selectMovie(showing.getMovieId());

Rule rule = findRule(showing, movie);

}

3 if (Rule 이 존재하면) {

Discount를 읽어 요금 할인된 요금 계산

} else {

Movie에 저장되어 있는 정액 요금 사용

}

Money fee = movie.getFee();

if (rule != null) {

fee = calculateFee(movie);

}

private Money calculateFee(Movie movie) {

4 Reservation 생성 Discount 후 데이터베이스 discount 저장= discountDAO.selectDiscount(movie.getId());

if (discount.isAmountType()) {

return movie.getFee().minus(Money.wons(discount.getFee()));

} else if (discount.isPercentType()) {

return movie.getFee().minus(

movie.getFee().times(discount.getPercent()));

}

}

return movie.getFee();

24 / 문서의 제목


데이터를 사용한 예매 프로세스 구현

@Override

public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {

Showing showing = showingDAO.selectShowing(showingId);

Movie movie = movieDAO.selectMovie(showing.getMovieId());

Rule rule = findRule(showing, movie);

Money fee = movie.getFee();

if (rule != null) {

fee = calculateFee(movie);

}

4 Reservation 생성 후 데이터베이스 저장

Reservation result = makeReservation(customerId, showingId, audienceCount, fee);

reservationDAO.insert(result);

}

return result;

private Reservation makeReservation(int customerId, int showingId,

int audienceCount, Money payment) {

Reservation result = new Reservation();

result.setCustomerId(customerId);

result.setShowingId(showingId);

result.setAudienceCount(audienceCount);

result.setFee(payment);

}

return result;

25 / 문서의 제목


중앙 집중식 Centralized 제어 스타일

:Reservation

Service

:Showing

DAO

showing

:Showing

:Rule

DAO

rules

:Rule

:Discount

DAO

discount

:Discount

:Reservation

reserveShowing()

showing = selectShowing()

rules = selectRules()

* isAccepted()

discount = selectDiscount()

isAmountType()

getFee()

new

26 / 문서의 제목


아키텍처 패턴

Transaction Script

27 / 문서의 제목


3. 책임-주도 설계


책임 Responsibility

29 / 문서의 제목


예매 생성 책임

예매 생성에 필요한 정보의 EXPERT에게 할당 Creator

Showing

상영 정보를 알고 있다

예매 정보를 생성한다

30 / 문서의 제목


가격 계산 책임

영화 가격 정보를 알고 있는 EXPERT에 할당Information Expert

Movie

Showing

상영 정보를 알고 있다

예매 정보를 생성한다

Movie

Customer

영화정보를 알고 있다

가격을 계산한다

31 / 문서의 제목


할인율 계산 책임

할인율을 적용할 STRATEGY 객체 추가

Movie

Showing

상영 정보를 알고 있다

예매 정보를 생성한다

Movie

Customer

영화정보를 알고 있다 DiscountStrategy

가격을 계산한다

DiscountStrategy

할인율 정책을 알고 있다

할인된 가격을 계산한다

32 / 문서의 제목


할인 여부를 판단할 책임

할인 정책을 판단하기 위한 SPECIFICATION 객체 추가

Movie

Showing

상영 정보를 알고 있다

예매 정보를 생성한다

Movie

Customer

영화정보를 알고 있다 DiscountStrategy

가격을 계산한다

DiscountStrategy

할인율 정책을 알고 있다

할인된 가격을 계산한다

Rule

Rule

할인 정책을 알고 있다

할인 여부를 판단한다

Showing

33 / 문서의 제목


Rich Domain Model

상속 inheritance 과 다형성 polymorphism 의 활용

Reservation

Shwoing

reserve(Customer, int):Reservation


Customer

Movie

calculateFee(Showing):Money

DiscountStrategy

calculateFee(Showing):Money

Rule

isStatisfiedBy(Showing):boolean

AmountStrategy PercentStrategy NonDiscountStrategy SequenceRule TimeOfDayRule

34 / 문서의 제목


데이터에 대한 걱정은 잠시 꺼두셔도 좋습니다

35 / 문서의 제목


책임 기반 구현

public class Showing {

public Reservation reserve(Customer customer, int audienceCount) {

return new Reservation(customer, this, audienceCount);

}

}

public class Reservation {

public Reservation(Customer customer, Showing showing, int audienceCount) {

this.customer = customer;

this.showing = showing;

this.fee = showing.calculateFee().times(audienceCount);

this.audienceCount = audienceCount;

}

}

public class Showing {

public Money calculateFee() {

return movie.calculateFee(this);

}

}

public class Movie {

public Money calculateFee(Showing showing) {

return discountStrategy.calculateFee(showing);

}

}

36 / 문서의 제목


책임 기반 구현

public abstract class DiscountStrategy {

public Money calculateFee(Showing showing) {

for(Rule each : rules) {

if (each.isStatisfiedBy(showing)) {

return getDiscountedFee(showing);

}

}

}

return showing.getFixedFee();

abstract protected Money getDiscountedFee(Showing showing);

public abstract class Rule {

abstract public boolean isStatisfiedBy(Showing showing);

}

public class SequenceRule extends Rule {

public boolean isStatisfiedBy(Showing showing) {

return showing.isSequence(sequence);

}

} public class TimeOfDayRule extends Rule {

public boolean isStatisfiedBy(Showing showing) {

return showing.isPlayingOn(dayOfWeek) &&

Interval.closed(startTime, endTime)

.includes(showing.getPlayngInterval());

}

}

37 / 문서의 제목


책임 기반 구현

public abstract class DiscountStrategy {

public Money calculateFee(Showing showing) {

for(Rule each : rules) {

if (each.isStatisfiedBy(showing)) {

return getDiscountedFee(showing);

}

}

}

return showing.getFixedFee();

abstract protected Money getDiscountedFee(Showing showing);

public class AmountDiscountStrategy extends DiscountStrategy {

protected Money getDiscountedFee(Showing showing) {

return showing.getFixedFee().minus(discountAmount);

public class NonDiscountStrategy extends DiscountStrategy {

}

protected Money getDiscountedFee(Showing showing) {

}

public returnclass showing.getFixedFee();

PercentDiscountStrategy extends DiscountStrategy {

} protected Money getDiscountedFee(Showing showing) {

} return showing.getFixedFee().minus(showing.getFixedFee().times(percent));

}

}

38 / 문서의 제목


위임식 delegated , 분산식 dispersed 제어 스타일

:Showing

:Reservation

:Movie

:DiscountStrategy

:Rule

reserve()

new

calculateFee()

calculateFee()

calculateFee()

* isStatisfied()

39 / 문서의 제목


아키텍처 패턴

Domain Model

40 / 문서의 제목


Transaction Script의 단점

새로운 할인 정책 추가

@Override

public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {

Showing showing = showingDAO.selectShowing(showingId);

Movie movie = movieDAO.selectMovie(showing.getMovieId());

Rule rule = findRule(showing, movie);

}

Money fee = movie.getFee();

if (rule != null) {

fee = calculateFee(movie);

}

private Money calculateFee(Movie movie) {

Discount discount = discountDAO.selectDiscount(movie.getId());

Reservation result = makeReservation(customerId, showingId, audienceCount, fee);

reservationDAO.insert(result);

if (discount.isAmountType()) {

return movie.getFee().minus(Money.wons(discount.getFee()));

} else if (discount.isPercentType()) {

return result;

}

return movie.getFee().minus(

movie.getFee().times(discount.getPercent()));

}

return movie.getFee();

41 / 문서의 제목


Transaction Script의 단점

기졲 코드 수정

@Override

public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {

Showing showing = showingDAO.selectShowing(showingId);

Movie movie = movieDAO.selectMovie(showing.getMovieId());

Rule rule = findRule(showing, movie);

}

Money fee = movie.getFee();

if (rule != null) {

fee = calculateFee(movie);

}

private Money calculateFee(Movie movie) {

Discount discount = discountDAO.selectDiscount(movie.getId());

Reservation result = makeReservation(customerId, showingId, audienceCount, fee);

reservationDAO.insert(result);

if (discount.isAmountType()) {

return movie.getFee().minus(Money.wons(discount.getFee()));

} else if (discount.isPercentType()) {

return result;

return movie.getFee().minus(

movie.getFee().times(discount.getPercent()));

} else if (discount.isMilleageType()) {

return movie.getFee().minus(

movie.getMileageBaseAmount().times(discount.getMileageFactor()));

}

}

return movie.getFee();

42 / 문서의 제목


Rich Domain Model의 장점

새로운 할인 정책 추가

Reservation

Shwoing

reserve(Customer, int):Reservation


Customer

Movie

calculateFee(Showing):Money

DiscountStrategy

calculateFee(Showing):Money

Rule

isStatisfiedBy(Showing):boolean

AmountStrategy PercentStrategy NonDiscountStrategy SequenceRule TimeOfDayRule

43 / 문서의 제목


Rich Domain Model의 장점

OCPOpen-Closed Principle

Reservation

Shwoing

reserve(Customer, int):Reservation


Customer

Movie

calculateFee(Showing):Money

DiscountStrategy

calculateFee(Showing):Money

Rule

isStatisfiedBy(Showing):boolean

AmountStrategy PercentStrategy NonDiscountStrategy MileageStrategy SequenceRule TimeOfDayRule

44 / 문서의 제목


4. 아키텍처 & 프레임워크


Layered Architecture

User Interface

Service

Domain

Infrastructure

46 / 문서의 제목


도메인 레이어 캡슐화

47 / 문서의 제목


Layered Architecture

User Interface

Service

Domain

Infrastructure

48 / 문서의 제목


Service Layer

• 애플리케이션 경계

• 도메인 레이어의 재사용성 촉짂


ReservationService


CustomerRepository

reserveShowing(customerId, showingId, audienceCount)


ShowingRepository

ReservationServiceImpl

reserveShowing(customerId, showingId, audienceCount)


ReservationRepository

Shwoing

49 / 문서의 제목


Service Layer

• Operation Script

• Not Transaction Script


ReservationService


CustomerRepository

reserveShowing(customerId, showingId, audienceCount)

@Override

@Transactional(propagation=Propagation.REQUIRED)

public Reservation reserveShowing(int reserverId, int showingId,

int audienceCount) {

ShowingRepository

Customer reserver = customerRepository.find(reserverId);

Showing showing = showingRepository.find(showingId);

ReservationServiceImpl

Reservation reservation = showing.reserve(reserver, audienceCount);

reservationRepository.save(reservation);

reserveShowing(customerId, return reservation; showingId, audienceCount)


}

ReservationRepository

Shwoing

50 / 문서의 제목


도메인 레이어 의졲성 관리

User Interface

Service

Domain

Infrastructure

51 / 문서의 제목


순수한 객체

POJO

Plain Old Java Object

52 / 문서의 제목


POJO의 3대 요소

Dependency

Injection

Aspect-Oriented

Programming

Annotation

53 / 문서의 제목


비침투적인 Non-Intrusive 프레임워크

• POJO 개발을 위한 젂제조건

• Lightweight Framework

54 / 문서의 제목


Dependency Injection

• 객체 간의 의졲성 관리 이슈로부터 도메인 레이어 보호

• 구성-사용 분리 원리the principle of separating configuration from use


ReservationService

reserveShowing(customerId, showingId, audienceCount)


CustomerRepository

ReservationServiceImpl

reserveShowing(customerId, showingId, audienceCount)

CustomerRepositoryImpl

55 / 문서의 제목


Dependency Injection – Spring


ReservationService

reserveShowing(customerId, showingId, audienceCount)


CustomerRepository

ReservationServiceImpl

reserveShowing(customerId, showingId, audienceCount)

CustomerRepositoryImpl




56 / 문서의 제목


Impedance Mismatch

• 객체 모델과 DB 스키마 간의 불일치

• 객체 모델과 DB 스키마 간의 변환 계층 필요

RULE

Amount

Strategy

DiscountStrategy

Percent

Strategy

NonDiscount

Strategy

Sequence

Rule

Rule

TimeOfDay

Rule

DISCOUNT

MOVIE_ID(FK)

DISCOUNT_TYPE

FEE_AMOUNT

FEE_CURRENCY

PERCENT

ID

DISCOUNT_ID(FK)

POSITION

RULE_TYPE

DAY_OF_WEEK

START_TIME

END_TUME

SEQUENCE

57 / 문서의 제목


DATA MAPPER

• 객체 모델과 DB 스키마 간의 독립성 유지

• 도메인 객체는 DB에 대해 독립적

RULE

SequenceRule

Rule

TimeOfDayRule

RuleMapper

insert

Update

delete

ID

DISCOUNT_ID(FK)

POSITION

RULE_TYPE

DAY_OF_WEEK

START_TIME

END_TUME

SEQUENCE

58 / 문서의 제목


O/R MAPPER - Hibernate

RULE

Rule

ID

SequenceRule

TimeOfDayRule

DISCOUNT_ID(FK)

POSITION

RULE_TYPE

DAY_OF_WEEK

START_TIME

END_TUME

SEQUENCE

















59 / 문서의 제목


트랜잭션 경계

User Interface

Begin TX

Commit

Rollback

Service

Domain

Infrastructure

60 / 문서의 제목


AOPAspect Oriented Programming

User Interface

Begin TX

ASPECT

Commit

Rollback

Service

Domain

Infrastructure

61 / 문서의 제목


Spring AOP & Annotation


User Interface

Service

Begin TX

ASPECT




Commit

Rollback

@Override

@Transactional(propagation=Propagation.REQUIRED)

public Reservation Domain reserveShowing(int reserverId, int showingId, int audienceCount) {

Customer reserver = customerRepository.find(reserverId);

Showing showing = showingRepository.find(showingId);

Reservation reservation = showing.reserve(reserver, audienceCount);

reservationRepository.save(reservation);

return reservation;

}

Infrastructure

62 / 문서의 제목


5. 결롞


Rich Domain Model

• 설계 관점이지 기술 관점이 아님

• 훌륭한 객체 지향 설계 지침을 따를 것

64 / 문서의 제목


그러나 기술적인 제약 사항 역시 중요

• 비침투적인 프레임워크를 사용하라

• 프레임워크의 제약 사항을 파악하라

• 프레임워크의 제약 사항에 따라 구현 가능하도록 아키텍처를 수정하라

가지고 있는 도구 또한 아키텍처에 영향을 준다는 사실을 알 수 있다. 때로는 아키

텍처를 바탕으로 도구를 선택할 수 있으며, 이롞적으로는 그것이 올바른 방법이다.

그러나 실제로는 도구에 아키텍처를 맞추어야 한다.

- Martin Fowler

65 / 문서의 제목


Rich Domain Model을 자제해야 하는 경우

비즈니스 로직이 단순하고 개발 기간이 짧은 경우

비침투적인 프레임워크를 사용할 수 없는 경우

비침투적인 프레임워크에 대한 경험이 부족한 경우

O/R Mapper를 사용할 수 없는 경우

객체 지향 분석/설계 경험이 부족한 경우

66 / 문서의 제목


첨언

Be Pragmatic

67 / 문서의 제목


Thank you.


Question.


참고자료

- Patterns of Enterprise Application Architecture, Martin Fowler, Addison-Wesley, 2002

- Domain-Driven Design, Eric Evans, Addison-Wesley, 2003

- Expert One-on-One J2EE Development without EJB, Rod Johnson, Wrox, 2004

- Applying UML and Patterns 3 rd Edition, Craig Larman, Prentice Hall, 2004

- Agile Software Development, Principles, Patterns, and Practices, Robert C. Martin,

Prentice Hall, 2002

- Object Design : Roles, Responsibilities, and Collaborations , Rebecca Wirfs-Brock,

Alan McKean, Addison-Wesley,2002

- POJOs in Action, Chris Richardson, Manning, 2006

- Java Persistence with Hibernate, Christian Bauer, Gavin King, Manning, 2006

- Spring in Action 2 nd Edition, Craig Walls, Manning, 2007

- The New Holy Trinity, Ramnivas Laddad,

http://www.aspectprogrammer.org/blogs/adrian/2005/03/the_new_holy_tr.html

70 / 문서의 제목

More magazines by this user
Similar magazines