DDD 준비 서문래

64
DDD 준비 @서문래 최범균([email protected]), 2017-01-20

Transcript of DDD 준비 서문래

Page 1: DDD 준비 서문래

DDD 준비 @서문래

최범균([email protected]), 2017-01-20

Page 2: DDD 준비 서문래

발표자

• 최범균, [email protected]

• 주로자바로먹고살며,잡다한분야에관심

• 코딩잘하고,글잘쓰고싶은개발자

2DDD 소개@서문래

Page 3: DDD 준비 서문래

내용

• 내용• 언어,모델

• 아키텍처

• DDD소개

• 대상• DDD에관심있는개발자

3DDD 소개@서문래

Page 4: DDD 준비 서문래

# 언어와 모델

• 언어

• 도메인

• 모델

4DDD 소개@서문래

Page 5: DDD 준비 서문래

언어

• 언어매우중요

• 일관되지않은언어사용개발지옥• 완전히다른단어, (거의)같은것

• (거의)같은 단어,미세한의미차이

• 일관된사용해석불필요비용감소• 코드,문서,대화에서일관된언어사용

• 유비쿼터스언어(Ubiquitous Language)

5DDD 소개@서문래

Page 6: DDD 준비 서문래

이해하기 쉬운 용어

• 향후코드이해에유리한용어선택• 가능하면간결한단어

• 어려운영어,모호한영어< 발음대로영어표기• 예 : CivilAppeal < Minwon

• 예 :Kind, Type, Class, …< Gubun

6DDD 소개@서문래

Page 7: DDD 준비 서문래

언어 = 도메인, 컨텍스트

• 언어는특정도메인이나컨텍스트에서의미• 같은대상도도메인에따라다른용어

• 카탈로그의고객,주문의구매자

• 같은용어도도메인에따라다른의미• 배송의상품은실물,카탈로그의상품은정보

• 도메인에맞는용어선택에는노력필요• 도메인전문과와의대화

• 대화에출현한단어와(레거시)코드가불일치하면바꾸는노력

7DDD 소개@서문래

Page 8: DDD 준비 서문래

언어의 발전

• 도메인에대한새로운이해• 코드에반영, 언어에반영

public class Order {

public void changeShippingInfo(ShippingInfo newShippingInfo) {

if (state == OrderState.PAYMENT_WAITING ||state == OrderState..PREPARING) {this.shippingInfo = newShippingInof;

} else {throw new IllegalStateException();

}}

public class Order {

public void changeShippingInfo(ShippingInfo newShippingInfo) {

verifyNotYetShipped();this.shippingInfo = newShippingInof;

}

public void cancel() {verifyNotYetShipped();this.state = OrderState.CANCELED;

}

private void verifyNotYetShipped() {if (state != OrderState.PAYMENT_WAITING &&

state != OrderState.PREPARING)throw new IllegalStateException();

}

"결제대기거나물건준비중일때"배송지를변경할수있다.

"배송을아직하지않았으면"배송지를변경할수있다, 주문을취소할수있다.

8DDD 소개@서문래

Page 9: DDD 준비 서문래

추상화

• 개념이나표현을정의하는과정• 구체적인용어

• 요구사항과대화속에서도출

• 이해가깊어지면새로운개념발견

• 추상화언어정의• 알맞은언어선택을할수있도록도와줌

9DDD 소개@서문래

Page 10: DDD 준비 서문래

도메인Domain

소프트웨어로 해결할 문제 영역

온라인 쇼핑

10DDD 소개@서문래

Page 11: DDD 준비 서문래

도메인 모델

• 도메인의구성요소를개념적으로표현한것• 도메인의이해를바탕으로만들어짐• 구현과정에서점진적으로상세화

• 소프트웨어모델

• 다양한관점에서의모델• 정적인모델

• 클래스다이어그램, ER다이어그램

• 동적인모델• 커뮤니케이션다이어그램, 시퀀스다이어그램• 상태다이어그램

11DDD 소개@서문래

Page 12: DDD 준비 서문래

단순 데이터 모델

• 일부개념이사라짐• 단순속성나열

• 반쪽• 중요도메인기능/제약

이드러나지않음

Order-------------------------id:String-state:String-totalAmounts:int-receiverName:String-receiverPhoneNumber;String-shipAddr1:String-shipAddr2:String-shipZipcode:String-ordererName:String-ordererId:String

Product---------------id:String-listPrice:int

12DDD 소개@서문래

Page 13: DDD 준비 서문래

도메인 모델에 필요한 것

• 표현력• 최대한중요개념이드러나도록

• 도메인용어를최대한모델에반영• 불필요한번역/해석이발생하지않도록모델구축

• 기능• 객체모델,함수

• 동적측면/제약조건표현• 상태다이어그램등을활용

13DDD 소개@서문래

Page 14: DDD 준비 서문래

도메인 모델 예

14DDD 소개@서문래

Page 15: DDD 준비 서문래

도메인 모델 예

15DDD 소개@서문래

Page 16: DDD 준비 서문래

# 아키텍처

• 영역

• DIP

• 인프라

16DDD 소개@서문래

Page 17: DDD 준비 서문래

연통 배관 * 계층 구조

UI 서비스 DAO

UI 서비스 DAO

UI 서비스 DAO

• 참고 자료: http://www.slideshare.net/gyumee/ss-55616001

17DDD 소개@서문래

Page 18: DDD 준비 서문래

고수준 모듈, 저수준 모듈

• 고수준모듈• 어떤의미있는

단일기능을제공하는모듈

• 저수준모듈• 고수준모듈의

기능을구현하기위해필요한하위기능의실제구현

18DDD 소개@서문래

Page 19: DDD 준비 서문래

의존의 방향

• 고수준저수준으로의존• 저수준의변경에따라고수준이영향을받을수있음

19DDD 소개@서문래

Page 20: DDD 준비 서문래

DIP(Dependency Inversion Principle)

• 고수준모듈이저수준모듈에의존하지않고저수준모듈이고수준모듈에의존함• 의존방향을"고수준저수준"에서

"저수준고수준"으로변경

고수준

저수준

20DDD 소개@서문래

Page 21: DDD 준비 서문래

DIP 주의사항

21DDD 소개@서문래

Page 22: DDD 준비 서문래

(DDD의) 영역

영역 설명

표현(UI) 사용자의 요청을 해석해서 다른 영역에 처리를위임하고, 결과를 사용자에 알맞게 변환해서전달한다.사용자에게 보여지는 흐름을 제어한다.

응용 사용자의 요청을 처리할 기능을 구현한다. 처리 흐름을 구현하며, 도메인 로직은 도메인 영역에 위임한다.

도메인 도메인 로직을 구현한다. 엔티티, 밸류, 애그리거트, 리포지토리, 도메인 서비스가 위치한다.

인프라스트럭처 DBMS 연동, REST 클라이언트와 같은 구현 기술을 다룬다. DIP 구조에서 저수준 모듈이 이영역에 위치한다.

22DDD 소개@서문래

Page 23: DDD 준비 서문래

아키텍처에서의 고수준/저소준

23DDD 소개@서문래

Page 24: DDD 준비 서문래

DIP와 DDD의 하위 도메인 간 연결

도메인 인프라UI/응용

도메인 인프라UI/응용

24DDD 소개@서문래

Page 25: DDD 준비 서문래

인프라스트럭처

• 기반구현기술제공

• 응용서비스나도메인서비스구현• 외부시스템과의연동이나특정기술에기반한구현

• 리포지토리구현

• 외부REST API 연동구현

• 메일클라이언트연동구현

• DIP고려• 응용이나도메인에위치할인터페이스를구현관점에서

작성하지않도록함

25DDD 소개@서문래

Page 26: DDD 준비 서문래

# DDD 소개

• 도메인모델구성요소

26DDD 소개@서문래

Page 27: DDD 준비 서문래

세 개의 축

LanguageBoundedContext

TacticalPattern

27DDD 소개@서문래

Page 28: DDD 준비 서문래

DDD의 모델 구성 요소

• 기본모델• 엔티티

• 밸류

• 모델의묶음• 애그리거트

• 기능• 객체모델

• 도메인서비스

• 영속• 리포지토리

28DDD 소개@서문래

Page 29: DDD 준비 서문래

엔티티로 중심 모델 만들기

• 엔티티의특징• 식별자(ID)를가짐

• 식별자는바뀌지않음

• 데이터/상태를가짐

• 자기만의라이프사이클을가짐

• (주로)엔티티의상태에따라다르게동작함

• (주로)DB테이블과1대1관계를가짐

• 대표적인예• 주문,회원,제품

29DDD 소개@서문래

Page 30: DDD 준비 서문래

밸류로 모델 표현력 증가하기

• 개념적으로하나인데이터집합을표현• 예,주소,배송지,돈

Order-------------------------id:String-state:String-totalAmounts:int-receiverName:String-receiverPhoneNumber;String-shipAddr1:String-shipAddr2:String-shipZipcode:String-ordererName:String-ordererId:String

Address-------------------------addr1:String-addr2:String-zipcode:String

Receiver-------------------------name:String-phoneNumber;StringShippingInfo

--------------------receiver:Receiver-address:Address

Order-------------------------id:String-state:String-totalAmounts:Money-shippingInfo:ShippingInfo-orderer:Orderer

Money-----------value:int

Orderer-----------name:String-id:String

30DDD 소개@서문래

Page 31: DDD 준비 서문래

모델에 기능 넣기

• 메서드로기능과제약표현

public class Order {

public void changeShippingInfo(ShippingInfo newShippingInfo) {verifyNotYetShipped();setShippingInfo(newShippingInfo);

}

private void verifyNotYetShipped() {if (state != OrderState.PAYMENT_WAITING || … )

throw new IllegalArgumentException("already shipped");}

private void setShippingInfo(ShippingInfo si) {this.shippingInfo = si;

}…

31DDD 소개@서문래

Page 32: DDD 준비 서문래

도메인 모델의 공개 set 메서드 없애기

• 도메인코드의공개 set/get메서드는• 핵심개념이나의도를사라지게만듬

• completePayment() vs setOrderState()

• 절차지향이되기쉬움• verifyNotYetShipped() vs getOrderState() != WAITING_PAYMENT

• set/get메서드기능/제약을표현하는메서드

• 필요한경우get과 is같은데이터제공메서드사용

• 영역경계간데이터를전달하기위한DTO와구분

32DDD 소개@서문래

Page 33: DDD 준비 서문래

# 애그리거트

• 애그리거트란

• 애그리거트크기, 경계

33DDD 소개@서문래

Page 34: DDD 준비 서문래

애그리거트Aggregate로 모으기

• 개별모델을상위수준에서묶어주는단위

34DDD 소개@서문래

Page 35: DDD 준비 서문래

애그리거트

• 상위수준에서모델을이해하는데도움

• 도메인규칙/일관성을관리하는단위• 복잡한도메인을단순화

• 한애그리거트에속한객체는유사한라이프사이클• 특히,함께생성되고함께삭제됨

35DDD 소개@서문래

Page 36: DDD 준비 서문래

애그리거트와 경계

• 한애그리거트에속한객체는다른애그리거트에속하지않음• 함께변경되는객체는한애그리거트에속할가능성높음

• 예,배송지주소,주문정보(주문개수)

• 한애그리거트는자기자신만관리• 다른애그리거트를직접변경하지않음

• 예,주문애그리거트에서회원애그리거트를변경하지않음

36DDD 소개@서문래

Page 37: DDD 준비 서문래

애그리거트 루트

• 애그리거트의책임자• 애그리거트에속한

객체가일관된상태를유지하도록관리하는책임을가진객체

• 애그리거트의모든객체는루트에직/간접적으로속함

37DDD 소개@서문래

Page 38: DDD 준비 서문래

애그리거트 루트

• 도메인규칙과일관성을유지하는주체• 애그리거트루트를통해서만애그리거트내부상태변경

• 애그리거트외부에기능을제공

public class Order {

public void changeShippingInfo(ShippingInfo newShippingInfo) {verifyNotYetShipped();setShippingInfo(newShippingInfo);

}

private void verifyNotYetShipped() {if (state != OrderState.PAYMENT_WAITING || … )

throw new IllegalArgumentException("already shipped");}

private void setShippingInfo(ShippingInfo si) {this.shippingInfo = si;

}…

38DDD 소개@서문래

Page 39: DDD 준비 서문래

애그리거트 루트

• 애그리거트에속한객체를이용해서기능구현• 애그리거트에속한다른

객체에위임

• 애그리거트에속한다른객체의상태를조회

• 밸류객체는객체자체를교체하는방법이쉬움

public class Member {private Password password;

public void changePassword(String curPw,String newPw) {

if (!password.match(curPw)) { // 위임throw new NotMatchingException();

}// 상태 변경this.password = new Password(newPw);

}…

39DDD 소개@서문래

Page 40: DDD 준비 서문래

애그리거트의 크기, 경계

• 주로한개엔티티와소수의밸류객체로구성• N개엔티티객체로구성되는경우는흔치않음

• has로보이는1-N관계는서로다른애그리거트인지확인필요

• M-N관계는연관객체가어느애그리거트에속하는지확인필요

40DDD 소개@서문래

Page 41: DDD 준비 서문래

# 리포지토리

• 리포지토리

• 스펙

• 리포지토리와애그리거트간연관

41DDD 소개@서문래

Page 42: DDD 준비 서문래

리포지토리 인터페이스

• 리포지토리는애그리거트 (루트)단위로존재• Order OrderRepository, Member MemberRepository

• 테이블단위로존재하는것이아님

• 리포지토리의주요메서드• save(User order)

• User findOne(String email)

• List<User> findByJoinDateBetween(Date from, Date to)

• List<User> findBySpec(Specification spec)

• Page<User> findByName(String name)

• void remove(User user)

42DDD 소개@서문래

Page 43: DDD 준비 서문래

리포지토리 구현 기술: JPA

• 애그리거트를RDBMS와매핑하기에무난한기술

• 애노테이션기반설정 (XML보다자바설정선호)

• 밸류에대한매핑설정/확장지원• @Embeddable

• AttributeConverter

• 콜렉션지원

• 엔티티간1-1, 1-N, N-M지원

• 연관엔티티에대한영속성전파

43DDD 소개@서문래

Page 44: DDD 준비 서문래

Spring Data JPA

• 반복작업제거• 인터페이스만만들면구현객체는런타임에생성

• 정해진규칙에따라인터페이스정의

• JPQL, 네이티브쿼리실행가능

• 페이징,스펙(+QueryDSL)지원

44DDD 소개@서문래

Page 45: DDD 준비 서문래

스펙

• 검색조건을표현• 개별검색조건을하나의스펙으로표현

• 검색조건의의미가잘드러남

• And, Or를이용한스펙조합

• 주로조회기능에서활용

• Spring Data JPA스펙지원• 설명: http://goo.gl/7R4SYO

Specification<OnlineTest> nameSpec = nameLike("중간고사");Specification<OnlineTest> yearSpec = yearBetween(2011, 2016);Specifications<OnlineTest> specs = where(nameSpec);specs = specs.and(yearBetween);List<OnlineTest> test = onlineRepository.findAll(specs);

45DDD 소개@서문래

Page 46: DDD 준비 서문래

애그리거트는 완전체

• 리포지토리는완전한애그리거트를다룸• 로딩시점에애그리거트에속한모든연관객체로딩

• 즉,엔티티나콜렉션에대해EAGER로딩기본사용• 기능구현에따라안전한경우에한해LAZY로딩

• 저장시점에애그리거트에속한모든연관객체저장

• 삭제시점에애그리거트에속한모든연관객체삭제

save(onlineTest)

find(id)

46DDD 소개@서문래

Page 47: DDD 준비 서문래

애그리거트 간 연관

• 애그리거트간연관 :애그리거트루트를참조

• 직접참조보다는 ID참조선호• 몇가지이유

• 편한탐색오용방지

• 성능에대한고민제거

• 시스템확장시유리

public class Reserve {private Customer reserver;…

public class Reserve {private CustomerId reserverId;…

47DDD 소개@서문래

Page 48: DDD 준비 서문래

애그리거트 간 연관: ID 참조

• 연관된애그리거트와의조합이필요한기능• 상태변경기능

• 응용서비스에서조합처리

• 도메인서비스고려

• 여러애그리거트함께조회• 조회전용DAO고려

48DDD 소개@서문래

Page 49: DDD 준비 서문래

사고 방식 추가

• 조인, 데이터애그리거트,기능• 애그리거트단위로기능구현

• 기능에서객체의상태를변경

• 루트에서다른객체에위임

• 한개모델조회,명령• CQRS

• 조회전용DTO/DAO

49DDD 소개@서문래

Page 50: DDD 준비 서문래

# 응용 서비스

50DDD 소개@서문래

Page 51: DDD 준비 서문래

응용 서비스

• 사용자가요청한기능을실행하고결과를리턴• 일종의입력과출력을가진프로시저

• 도메인을사용해서기능구현

• 도메인영역과표현영역을연결해주는창구

51DDD 소개@서문래

Page 52: DDD 준비 서문래

응용 서비스의 구현

• 도메인객체를사용한기능구현• 리포지토리에서애그리거트루트를구하고

• 애그리거트루트의기능을실행

@Servicepublic class ActivateOperatorService {

private OperatorRepository operatorRepository;

@Transactionalpublic void activate(String operatorId) {

// 애그리거트 루트를 구하고Operator operator = operatorRepository.findOne(operatorId);

if (operator == null)throw new OperatorNotFoundException();

// 도메인 기능을 실행operator.activate();

}

52DDD 소개@서문래

Page 53: DDD 준비 서문래

응용 서비스의 입력과 출력

• 메서드파라미터• 기능을수행하는데필요한데이터

public class ChangePasswordService {

public void changePassword(String id, String curPw, String newPw) {

Member member = findMember(id);member.changePassword(curPw, newPw);

}...

public class PlaceOrderService {

public OrderNo placeOrder(OrderRequest req) {OrderNo id = createNextOrderNo();Order order = createOrder(id, req);orderRepository.save(order);return id;

}...

* 도메인의 엔티티를 서비스의 파라미터로 사용하지 말 것* 딱 들어맞는 경우로만 제한

53DDD 소개@서문래

Page 54: DDD 준비 서문래

응용 서비스의 입력과 출력

• 리턴• 요청처리결과를사용자에게보여줄경우응답제공

• 예,새로생성한주문의 ID,주문목록

• 고민거리• 도메인객체리턴vs UI에노출할데이터만담은객체리턴

• 구현편의성,팀표준, 성능등고려

• 조회전용모델검토

• 익셉션은실패를의미• 응용서비스나도메인에서발생

54DDD 소개@서문래

Page 55: DDD 준비 서문래

응용 서비스와 트랜잭션

• 트랜잭션담당

public class ChangePasswordService {

@Transactionalpublic void changePassword(ChangePasswordRequest request) {

Member member = findExistingMember(request.getMemberId());member.changePassword(

request.getCurrentPassword(), request.getNewPassword());

}...

* 고민거리 : Open Session In View

55DDD 소개@서문래

Page 56: DDD 준비 서문래

응용 서비스에 도메인 로직 넣지 않기

• 도메인로직이나제약을구현하면안됨!

public class ActivateOperatorService {private OperatorRepository operatorRepository;

@Transactionalpublic void activate(String operatorId) {

Operator operator = operatorRepository.findOne(operatorId);if (operator == null)

throw new OperatorNotFoundException();

if (operator.isDeleted() || operator.isActivated()) {throw new RuntimeException();

}operator.setActivated(true);

// operator.activate()에 들어가야 할 제약 검사}

56DDD 소개@서문래

Page 57: DDD 준비 서문래

몇 가지 고민할 것

• 서비스• 크기

• 도메인단위로기능몰아넣기vs 기능군별로서비스구분

• 인터페이스필요여부• 인터페이스 +구현클래스

• 그냥클래스만

• 서비스vs 표현vs도메인• 값검증

• 권한검사

57DDD 소개@서문래

Page 58: DDD 준비 서문래

# BOUNDED CONTEXT

58DDD 소개@서문래

Page 59: DDD 준비 서문래

BOUNDED CONTEXT

• 모델경계= 언어경계• 문백안에서의미

• 논리적인언어경계선

• 기능을제공하는물리적인시스템

• 조직구조를따라하위도메인에매핑

59DDD 소개@서문래

Page 60: DDD 준비 서문래

BOUNDED CONTEXT의 구조

60DDD 소개@서문래

Page 61: DDD 준비 서문래

BC 간 관계

• 서비스공급자,고객• 상류, 하류

• 공개호스트서비스Open Host Service

• 안티코럽션레이어Anticorruption Layer

• 공유커널Shared Kernel

• 독립방식Separate Way

61DDD 소개@서문래

Page 62: DDD 준비 서문래

컨텍스트 맵

62DDD 소개@서문래

Page 63: DDD 준비 서문래

# 정리

63DDD 소개@서문래

Page 64: DDD 준비 서문래

최범균 | [email protected] | http://javacan.tistory.com

64DDD 소개@서문래