DDD 준비 @서문래
최범균(madvirus@madvirus.net), 2017-01-20
발표자
• 최범균,madvirus@madvirus.net
• 주로자바로먹고살며,잡다한분야에관심
• 코딩잘하고,글잘쓰고싶은개발자
2DDD 소개@서문래
내용
• 내용
• 언어,모델
• 아키텍처
• DDD소개
• 대상
• DDD에관심있는개발자
3DDD 소개@서문래
# 언어와 모델
• 언어
• 도메인
• 모델
4DDD 소개@서문래
언어
• 언어매우중요
• 일관되지않은언어사용개발지옥
• 완전히다른단어,(거의)같은것
• (거의)같은 단어,미세한의미차이
• 일관된사용해석불필요비용감소
• 코드,문서,대화에서일관된언어사용
• 유비쿼터스언어(UbiquitousLanguage)
5DDD 소개@서문래
이해하기 쉬운 용어
• 향후코드이해에유리한용어선택
• 가능하면간결한단어
• 어려운영어,모호한영어<발음대로영어표기
• 예:CivilAppeal<Minwon
• 예:Kind,Type,Class,…<Gubun
6DDD 소개@서문래
언어 = 도메인, 컨텍스트
• 언어는특정도메인이나컨텍스트에서의미
• 같은대상도도메인에따라다른용어
• 카탈로그의고객,주문의구매자
• 같은용어도도메인에따라다른의미
• 배송의상품은실물,카탈로그의상품은정보
• 도메인에맞는용어선택에는노력필요
• 도메인전문과와의대화
• 대화에출현한단어와(레거시)코드가불일치하면바꾸는노력
7DDD 소개@서문래
언어의 발전
• 도메인에대한새로운이해
• 코드에반영,언어에반영
public class Order {
public voidchangeShippingInfo(
ShippingInfonewShippingInfo){
if (state== OrderState.PAYMENT_WAITING||
state== OrderState..PREPARING){
this.shippingInfo= newShippingInof;
} else {
thrownew IllegalStateException();
}
}
public class Order {
public voidchangeShippingInfo(
ShippingInfonewShippingInfo){
verifyNotYetShipped();
this.shippingInfo = newShippingInof;
}
public voidcancel() {
verifyNotYetShipped();
this.state= OrderState.CANCELED;
}
privatevoid verifyNotYetShipped(){
if (state!= OrderState.PAYMENT_WAITING&&
state!= OrderState.PREPARING)
throw new IllegalStateException();
}
"결제대기거나물건준비중일때"
배송지를변경할수있다.
"배송을아직하지않았으면"
배송지를변경할수있다,주문을취소할수있다.
8DDD 소개@서문래
추상화
• 개념이나표현을정의하는과정
• 구체적인용어
• 요구사항과대화속에서도출
• 이해가깊어지면새로운개념발견
• 추상화언어정의
• 알맞은언어선택을할수있도록도와줌
9DDD 소개@서문래
도메인Domain
소프트웨어로 해결할 문제 영역
온라인 쇼핑
10DDD 소개@서문래
도메인 모델
• 도메인의구성요소를개념적으로표현한것
• 도메인의이해를바탕으로만들어짐
• 구현과정에서점진적으로상세화
• 소프트웨어모델
• 다양한관점에서의모델
• 정적인모델
• 클래스다이어그램,ER다이어그램
• 동적인모델
• 커뮤니케이션다이어그램,시퀀스다이어그램
• 상태다이어그램
11DDD 소개@서문래
단순 데이터 모델
• 일부개념이사라짐
• 단순속성나열
• 반쪽
• 중요도메인기능/제약
이드러나지않음
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 소개@서문래
도메인 모델에 필요한 것
• 표현력
• 최대한중요개념이드러나도록
• 도메인용어를최대한모델에반영
• 불필요한번역/해석이발생하지않도록모델구축
• 기능
• 객체모델,함수
• 동적측면/제약조건표현
• 상태다이어그램등을활용
13DDD 소개@서문래
도메인 모델 예
14DDD 소개@서문래
도메인 모델 예
15DDD 소개@서문래
# 아키텍처
• 영역
• DIP
• 인프라
16DDD 소개@서문래
연통 배관 * 계층 구조
UI 서비스 DAO
UI 서비스 DAO
UI 서비스 DAO
• 참고 자료: http://www.slideshare.net/gyumee/ss-55616001
17DDD 소개@서문래
고수준 모듈, 저수준 모듈
• 고수준모듈
• 어떤의미있는
단일기능을
제공하는모듈
• 저수준모듈
• 고수준모듈의
기능을구현하기
위해필요한
하위기능의
실제구현
18DDD 소개@서문래
의존의 방향
• 고수준저수준으로의존
• 저수준의변경에따라고수준이영향을받을수있음
19DDD 소개@서문래
DIP(Dependency Inversion Principle)
• 고수준모듈이저수준모듈에의존하지않고
저수준모듈이고수준모듈에의존함
• 의존방향을"고수준저수준"에서
"저수준고수준"으로변경
고수준
저수준
20DDD 소개@서문래
DIP 주의사항
21DDD 소개@서문래
(DDD의) 영역
영역 설명
표현(UI) 사용자의 요청을 해석해서 다른 영역에 처리를
위임하고, 결과를 사용자에 알맞게 변환해서
전달한다.
사용자에게 보여지는 흐름을 제어한다.
응용 사용자의 요청을 처리할 기능을 구현한다. 처
리 흐름을 구현하며, 도메인 로직은 도메인 영
역에 위임한다.
도메인 도메인 로직을 구현한다. 엔티티, 밸류, 애그리
거트, 리포지토리, 도메인 서비스가 위치한다.
인프라스트럭처 DBMS 연동, REST 클라이언트와 같은 구현 기
술을 다룬다. DIP 구조에서 저수준 모듈이 이
영역에 위치한다.
22DDD 소개@서문래
아키텍처에서의 고수준/저소준
23DDD 소개@서문래
DIP와 DDD의 하위 도메인 간 연결
도메인 인프라UI/응용
도메인 인프라UI/응용
24DDD 소개@서문래
인프라스트럭처
• 기반구현기술제공
• 응용서비스나도메인서비스구현
• 외부시스템과의연동이나특정기술에기반한구현
• 리포지토리구현
• 외부RESTAPI연동구현
• 메일클라이언트연동구현
• DIP고려
• 응용이나도메인에위치할인터페이스를구현관점에서
작성하지않도록함
25DDD 소개@서문래
# DDD 소개
• 도메인모델구성요소
26DDD 소개@서문래
세 개의 축
Language
Bounded
Context
Tactical
Pattern
27DDD 소개@서문래
DDD의 모델 구성 요소
• 기본모델
• 엔티티
• 밸류
• 모델의묶음
• 애그리거트
• 기능
• 객체모델
• 도메인서비스
• 영속
• 리포지토리
28DDD 소개@서문래
엔티티로 중심 모델 만들기
• 엔티티의특징
• 식별자(ID)를가짐
• 식별자는바뀌지않음
• 데이터/상태를가짐
• 자기만의라이프사이클을가짐
• (주로)엔티티의상태에따라다르게동작함
• (주로)DB테이블과1대1관계를가짐
• 대표적인예
• 주문,회원,제품
29DDD 소개@서문래
밸류로 모델 표현력 증가하기
• 개념적으로하나인데이터집합을표현
• 예,주소,배송지,돈
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 소개@서문래
모델에 기능 넣기
• 메서드로기능과제약표현
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 소개@서문래
도메인 모델의 공개 set 메서드 없애기
• 도메인코드의공개set/get메서드는
• 핵심개념이나의도를사라지게만듬
• completePayment()vssetOrderState()
• 절차지향이되기쉬움
• verifyNotYetShipped()vsgetOrderState()!=WAITING_PAYMENT
• set/get메서드기능/제약을표현하는메서드
• 필요한경우get과is같은데이터제공메서드사용
• 영역경계간데이터를전달하기위한DTO와구분
32DDD 소개@서문래
# 애그리거트
• 애그리거트란
• 애그리거트크기,경계
33DDD 소개@서문래
애그리거트Aggregate로 모으기
• 개별모델을상위수준에서묶어주는단위
34DDD 소개@서문래
애그리거트
• 상위수준에서모델을이해하는데도움
• 도메인규칙/일관성을관리하는단위
• 복잡한도메인을단순화
• 한애그리거트에속한객체는유사한라이프사이클
• 특히,함께생성되고함께삭제됨
35DDD 소개@서문래
애그리거트와 경계
• 한애그리거트에속한객체는다른애그리거트에속
하지않음
• 함께변경되는객체는한애그리거트에속할가능성높음
• 예,배송지주소,주문정보(주문개수)
• 한애그리거트는자기자신만관리
• 다른애그리거트를직접변경하지않음
• 예,주문애그리거트에서회원애그리거트를변경하지않음
36DDD 소개@서문래
애그리거트 루트
• 애그리거트의책임자
• 애그리거트에속한
객체가일관된상태를
유지하도록관리하는
책임을가진객체
• 애그리거트의
모든객체는루트에
직/간접적으로속함
37DDD 소개@서문래
애그리거트 루트
• 도메인규칙과일관성을유지하는주체
• 애그리거트루트를통해서만애그리거트내부상태변경
• 애그리거트외부에기능을제공
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 소개@서문래
애그리거트 루트
• 애그리거트에속한객체를
이용해서기능구현
• 애그리거트에속한다른
객체에위임
• 애그리거트에속한다른
객체의상태를조회
• 밸류객체는객체자체를
교체하는방법이쉬움
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 소개@서문래
애그리거트의 크기, 경계
• 주로한개엔티티와소수의밸류객체로구성
• N개엔티티객체로구성되는경우는흔치않음
• has로보이는1-N관계는서로다른애그리거트인지
확인필요
• M-N관계는연관객체가어느애그리거트에속하는
지확인필요
40DDD 소개@서문래
# 리포지토리
• 리포지토리
• 스펙
• 리포지토리와애그리거트간연관
41DDD 소개@서문래
리포지토리 인터페이스
• 리포지토리는애그리거트(루트)단위로존재
• OrderOrderRepository,MemberMemberRepository
• 테이블단위로존재하는것이아님
• 리포지토리의주요메서드
• save(Userorder)
• UserfindOne(Stringemail)
• List<User>findByJoinDateBetween(Datefrom,Dateto)
• List<User>findBySpec(Specificationspec)
• Page<User>findByName(Stringname)
• voidremove(Useruser)
42DDD 소개@서문래
리포지토리 구현 기술: JPA
• 애그리거트를RDBMS와매핑하기에무난한기술
• 애노테이션기반설정(XML보다자바설정선호)
• 밸류에대한매핑설정/확장지원
• @Embeddable
• AttributeConverter
• 콜렉션지원
• 엔티티간1-1,1-N,N-M지원
• 연관엔티티에대한영속성전파
43DDD 소개@서문래
Spring Data JPA
• 반복작업제거
• 인터페이스만만들면구현객체는런타임에생성
• 정해진규칙에따라인터페이스정의
• JPQL,네이티브쿼리실행가능
• 페이징,스펙(+QueryDSL)지원
44DDD 소개@서문래
스펙
• 검색조건을표현
• 개별검색조건을하나의스펙으로표현
• 검색조건의의미가잘드러남
• And,Or를이용한스펙조합
• 주로조회기능에서활용
• SpringDataJPA스펙지원
• 설명: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 소개@서문래
애그리거트는 완전체
• 리포지토리는완전한애그리거트를다룸
• 로딩시점에애그리거트에속한모든연관객체로딩
• 즉,엔티티나콜렉션에대해EAGER로딩기본사용
• 기능구현에따라안전한경우에한해LAZY로딩
• 저장시점에애그리거트에속한모든연관객체저장
• 삭제시점에애그리거트에속한모든연관객체삭제
save(onlineTest) 
 find(id)
46DDD 소개@서문래
애그리거트 간 연관
• 애그리거트간연관:애그리거트루트를참조
• 직접참조보다는ID참조선호
• 몇가지이유
• 편한탐색오용방지
• 성능에대한고민제거
• 시스템확장시유리
public class Reserve {
private Customer reserver;
…
public class Reserve {
private CustomerId reserverId;
…
47DDD 소개@서문래
애그리거트 간 연관: ID 참조
• 연관된애그리거트와의조합이필요한기능
• 상태변경기능
• 응용서비스에서조합처리
• 도메인서비스고려
• 여러애그리거트함께조회
• 조회전용DAO고려
48DDD 소개@서문래
사고 방식 추가
• 조인,데이터애그리거트,기능
• 애그리거트단위로기능구현
• 기능에서객체의상태를변경
• 루트에서다른객체에위임
• 한개모델조회,명령
• CQRS
• 조회전용DTO/DAO
49DDD 소개@서문래
# 응용 서비스
50DDD 소개@서문래
응용 서비스
• 사용자가요청한기능을실행하고결과를리턴
• 일종의입력과출력을가진프로시저
• 도메인을사용해서기능구현
• 도메인영역과표현영역을연결해주는창구
51DDD 소개@서문래
응용 서비스의 구현
• 도메인객체를사용한기능구현
• 리포지토리에서애그리거트루트를구하고
• 애그리거트루트의기능을실행
@Service
public class ActivateOperatorService {
private OperatorRepository operatorRepository;
@Transactional
public void activate(String operatorId) {
// 애그리거트 루트를 구하고
Operator operator = operatorRepository.findOne(operatorId);
if (operator == null)
throw new OperatorNotFoundException();
// 도메인 기능을 실행
operator.activate();
}
52DDD 소개@서문래
응용 서비스의 입력과 출력
• 메서드파라미터
• 기능을수행하는데필요한데이터
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 소개@서문래
응용 서비스의 입력과 출력
• 리턴
• 요청처리결과를사용자에게보여줄경우응답제공
• 예,새로생성한주문의ID,주문목록
• 고민거리
• 도메인객체리턴vsUI에노출할데이터만담은객체리턴
• 구현편의성,팀표준,성능등고려
• 조회전용모델검토
• 익셉션은실패를의미
• 응용서비스나도메인에서발생
54DDD 소개@서문래
응용 서비스와 트랜잭션
• 트랜잭션담당
public class ChangePasswordService {
@Transactional
public void changePassword(ChangePasswordRequest request) {
Member member = findExistingMember(request.getMemberId());
member.changePassword(
request.getCurrentPassword(),
request.getNewPassword());
}
...
* 고민거리 : Open Session In View
55DDD 소개@서문래
응용 서비스에 도메인 로직 넣지 않기
• 도메인로직이나제약을구현하면안됨!
public class ActivateOperatorService {
private OperatorRepository operatorRepository;
@Transactional
public 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 소개@서문래
몇 가지 고민할 것
• 서비스
• 크기
• 도메인단위로기능몰아넣기vs기능군별로서비스구분
• 인터페이스필요여부
• 인터페이스 +구현클래스
• 그냥클래스만
• 서비스vs표현vs도메인
• 값검증
• 권한검사
57DDD 소개@서문래
# BOUNDED CONTEXT
58DDD 소개@서문래
BOUNDED CONTEXT
• 모델경계=언어경계
• 문백안에서의미
• 논리적인언어경계선
• 기능을제공하는물리
적인시스템
• 조직구조를따라하위
도메인에매핑
59DDD 소개@서문래
BOUNDED CONTEXT의 구조
60DDD 소개@서문래
BC 간 관계
• 서비스공급자,고객
• 상류,하류
• 공개호스트서비스
OpenHostService
• 안티코럽션레이어
AnticorruptionLayer
• 공유커널
SharedKernel
• 독립방식
SeparateWay
61DDD 소개@서문래
컨텍스트 맵
62DDD 소개@서문래
# 정리
63DDD 소개@서문래
끝
최범균 | madvirus@madvirus.net | http://javacan.tistory.com
64DDD 소개@서문래

DDD 준비 서문래

  • 1.
  • 2.
  • 3.
    내용 • 내용 • 언어,모델 •아키텍처 • DDD소개 • 대상 • DDD에관심있는개발자 3DDD 소개@서문래
  • 4.
    # 언어와 모델 •언어 • 도메인 • 모델 4DDD 소개@서문래
  • 5.
    언어 • 언어매우중요 • 일관되지않은언어사용개발지옥 •완전히다른단어,(거의)같은것 • (거의)같은 단어,미세한의미차이 • 일관된사용해석불필요비용감소 • 코드,문서,대화에서일관된언어사용 • 유비쿼터스언어(UbiquitousLanguage) 5DDD 소개@서문래
  • 6.
    이해하기 쉬운 용어 •향후코드이해에유리한용어선택 • 가능하면간결한단어 • 어려운영어,모호한영어<발음대로영어표기 • 예:CivilAppeal<Minwon • 예:Kind,Type,Class,…<Gubun 6DDD 소개@서문래
  • 7.
    언어 = 도메인,컨텍스트 • 언어는특정도메인이나컨텍스트에서의미 • 같은대상도도메인에따라다른용어 • 카탈로그의고객,주문의구매자 • 같은용어도도메인에따라다른의미 • 배송의상품은실물,카탈로그의상품은정보 • 도메인에맞는용어선택에는노력필요 • 도메인전문과와의대화 • 대화에출현한단어와(레거시)코드가불일치하면바꾸는노력 7DDD 소개@서문래
  • 8.
    언어의 발전 • 도메인에대한새로운이해 •코드에반영,언어에반영 public class Order { public voidchangeShippingInfo( ShippingInfonewShippingInfo){ if (state== OrderState.PAYMENT_WAITING|| state== OrderState..PREPARING){ this.shippingInfo= newShippingInof; } else { thrownew IllegalStateException(); } } public class Order { public voidchangeShippingInfo( ShippingInfonewShippingInfo){ verifyNotYetShipped(); this.shippingInfo = newShippingInof; } public voidcancel() { verifyNotYetShipped(); this.state= OrderState.CANCELED; } privatevoid verifyNotYetShipped(){ if (state!= OrderState.PAYMENT_WAITING&& state!= OrderState.PREPARING) throw new IllegalStateException(); } "결제대기거나물건준비중일때" 배송지를변경할수있다. "배송을아직하지않았으면" 배송지를변경할수있다,주문을취소할수있다. 8DDD 소개@서문래
  • 9.
    추상화 • 개념이나표현을정의하는과정 • 구체적인용어 •요구사항과대화속에서도출 • 이해가깊어지면새로운개념발견 • 추상화언어정의 • 알맞은언어선택을할수있도록도와줌 9DDD 소개@서문래
  • 10.
    도메인Domain 소프트웨어로 해결할 문제영역 온라인 쇼핑 10DDD 소개@서문래
  • 11.
    도메인 모델 • 도메인의구성요소를개념적으로표현한것 •도메인의이해를바탕으로만들어짐 • 구현과정에서점진적으로상세화 • 소프트웨어모델 • 다양한관점에서의모델 • 정적인모델 • 클래스다이어그램,ER다이어그램 • 동적인모델 • 커뮤니케이션다이어그램,시퀀스다이어그램 • 상태다이어그램 11DDD 소개@서문래
  • 12.
    단순 데이터 모델 •일부개념이사라짐 • 단순속성나열 • 반쪽 • 중요도메인기능/제약 이드러나지않음 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 소개@서문래
  • 13.
    도메인 모델에 필요한것 • 표현력 • 최대한중요개념이드러나도록 • 도메인용어를최대한모델에반영 • 불필요한번역/해석이발생하지않도록모델구축 • 기능 • 객체모델,함수 • 동적측면/제약조건표현 • 상태다이어그램등을활용 13DDD 소개@서문래
  • 14.
  • 15.
  • 16.
    # 아키텍처 • 영역 •DIP • 인프라 16DDD 소개@서문래
  • 17.
    연통 배관 *계층 구조 UI 서비스 DAO UI 서비스 DAO UI 서비스 DAO • 참고 자료: http://www.slideshare.net/gyumee/ss-55616001 17DDD 소개@서문래
  • 18.
    고수준 모듈, 저수준모듈 • 고수준모듈 • 어떤의미있는 단일기능을 제공하는모듈 • 저수준모듈 • 고수준모듈의 기능을구현하기 위해필요한 하위기능의 실제구현 18DDD 소개@서문래
  • 19.
    의존의 방향 • 고수준저수준으로의존 •저수준의변경에따라고수준이영향을받을수있음 19DDD 소개@서문래
  • 20.
    DIP(Dependency Inversion Principle) •고수준모듈이저수준모듈에의존하지않고 저수준모듈이고수준모듈에의존함 • 의존방향을"고수준저수준"에서 "저수준고수준"으로변경 고수준 저수준 20DDD 소개@서문래
  • 21.
  • 22.
    (DDD의) 영역 영역 설명 표현(UI)사용자의 요청을 해석해서 다른 영역에 처리를 위임하고, 결과를 사용자에 알맞게 변환해서 전달한다. 사용자에게 보여지는 흐름을 제어한다. 응용 사용자의 요청을 처리할 기능을 구현한다. 처 리 흐름을 구현하며, 도메인 로직은 도메인 영 역에 위임한다. 도메인 도메인 로직을 구현한다. 엔티티, 밸류, 애그리 거트, 리포지토리, 도메인 서비스가 위치한다. 인프라스트럭처 DBMS 연동, REST 클라이언트와 같은 구현 기 술을 다룬다. DIP 구조에서 저수준 모듈이 이 영역에 위치한다. 22DDD 소개@서문래
  • 23.
  • 24.
    DIP와 DDD의 하위도메인 간 연결 도메인 인프라UI/응용 도메인 인프라UI/응용 24DDD 소개@서문래
  • 25.
    인프라스트럭처 • 기반구현기술제공 • 응용서비스나도메인서비스구현 •외부시스템과의연동이나특정기술에기반한구현 • 리포지토리구현 • 외부RESTAPI연동구현 • 메일클라이언트연동구현 • DIP고려 • 응용이나도메인에위치할인터페이스를구현관점에서 작성하지않도록함 25DDD 소개@서문래
  • 26.
    # DDD 소개 •도메인모델구성요소 26DDD 소개@서문래
  • 27.
  • 28.
    DDD의 모델 구성요소 • 기본모델 • 엔티티 • 밸류 • 모델의묶음 • 애그리거트 • 기능 • 객체모델 • 도메인서비스 • 영속 • 리포지토리 28DDD 소개@서문래
  • 29.
    엔티티로 중심 모델만들기 • 엔티티의특징 • 식별자(ID)를가짐 • 식별자는바뀌지않음 • 데이터/상태를가짐 • 자기만의라이프사이클을가짐 • (주로)엔티티의상태에따라다르게동작함 • (주로)DB테이블과1대1관계를가짐 • 대표적인예 • 주문,회원,제품 29DDD 소개@서문래
  • 30.
    밸류로 모델 표현력증가하기 • 개념적으로하나인데이터집합을표현 • 예,주소,배송지,돈 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 소개@서문래
  • 31.
    모델에 기능 넣기 •메서드로기능과제약표현 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 소개@서문래
  • 32.
    도메인 모델의 공개set 메서드 없애기 • 도메인코드의공개set/get메서드는 • 핵심개념이나의도를사라지게만듬 • completePayment()vssetOrderState() • 절차지향이되기쉬움 • verifyNotYetShipped()vsgetOrderState()!=WAITING_PAYMENT • set/get메서드기능/제약을표현하는메서드 • 필요한경우get과is같은데이터제공메서드사용 • 영역경계간데이터를전달하기위한DTO와구분 32DDD 소개@서문래
  • 33.
    # 애그리거트 • 애그리거트란 •애그리거트크기,경계 33DDD 소개@서문래
  • 34.
  • 35.
    애그리거트 • 상위수준에서모델을이해하는데도움 • 도메인규칙/일관성을관리하는단위 •복잡한도메인을단순화 • 한애그리거트에속한객체는유사한라이프사이클 • 특히,함께생성되고함께삭제됨 35DDD 소개@서문래
  • 36.
    애그리거트와 경계 • 한애그리거트에속한객체는다른애그리거트에속 하지않음 •함께변경되는객체는한애그리거트에속할가능성높음 • 예,배송지주소,주문정보(주문개수) • 한애그리거트는자기자신만관리 • 다른애그리거트를직접변경하지않음 • 예,주문애그리거트에서회원애그리거트를변경하지않음 36DDD 소개@서문래
  • 37.
    애그리거트 루트 • 애그리거트의책임자 •애그리거트에속한 객체가일관된상태를 유지하도록관리하는 책임을가진객체 • 애그리거트의 모든객체는루트에 직/간접적으로속함 37DDD 소개@서문래
  • 38.
    애그리거트 루트 • 도메인규칙과일관성을유지하는주체 •애그리거트루트를통해서만애그리거트내부상태변경 • 애그리거트외부에기능을제공 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 소개@서문래
  • 39.
    애그리거트 루트 • 애그리거트에속한객체를 이용해서기능구현 •애그리거트에속한다른 객체에위임 • 애그리거트에속한다른 객체의상태를조회 • 밸류객체는객체자체를 교체하는방법이쉬움 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 소개@서문래
  • 40.
    애그리거트의 크기, 경계 •주로한개엔티티와소수의밸류객체로구성 • N개엔티티객체로구성되는경우는흔치않음 • has로보이는1-N관계는서로다른애그리거트인지 확인필요 • M-N관계는연관객체가어느애그리거트에속하는 지확인필요 40DDD 소개@서문래
  • 41.
    # 리포지토리 • 리포지토리 •스펙 • 리포지토리와애그리거트간연관 41DDD 소개@서문래
  • 42.
    리포지토리 인터페이스 • 리포지토리는애그리거트(루트)단위로존재 •OrderOrderRepository,MemberMemberRepository • 테이블단위로존재하는것이아님 • 리포지토리의주요메서드 • save(Userorder) • UserfindOne(Stringemail) • List<User>findByJoinDateBetween(Datefrom,Dateto) • List<User>findBySpec(Specificationspec) • Page<User>findByName(Stringname) • voidremove(Useruser) 42DDD 소개@서문래
  • 43.
    리포지토리 구현 기술:JPA • 애그리거트를RDBMS와매핑하기에무난한기술 • 애노테이션기반설정(XML보다자바설정선호) • 밸류에대한매핑설정/확장지원 • @Embeddable • AttributeConverter • 콜렉션지원 • 엔티티간1-1,1-N,N-M지원 • 연관엔티티에대한영속성전파 43DDD 소개@서문래
  • 44.
    Spring Data JPA •반복작업제거 • 인터페이스만만들면구현객체는런타임에생성 • 정해진규칙에따라인터페이스정의 • JPQL,네이티브쿼리실행가능 • 페이징,스펙(+QueryDSL)지원 44DDD 소개@서문래
  • 45.
    스펙 • 검색조건을표현 • 개별검색조건을하나의스펙으로표현 •검색조건의의미가잘드러남 • And,Or를이용한스펙조합 • 주로조회기능에서활용 • SpringDataJPA스펙지원 • 설명: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 소개@서문래
  • 46.
    애그리거트는 완전체 • 리포지토리는완전한애그리거트를다룸 •로딩시점에애그리거트에속한모든연관객체로딩 • 즉,엔티티나콜렉션에대해EAGER로딩기본사용 • 기능구현에따라안전한경우에한해LAZY로딩 • 저장시점에애그리거트에속한모든연관객체저장 • 삭제시점에애그리거트에속한모든연관객체삭제 save(onlineTest)   find(id) 46DDD 소개@서문래
  • 47.
    애그리거트 간 연관 •애그리거트간연관:애그리거트루트를참조 • 직접참조보다는ID참조선호 • 몇가지이유 • 편한탐색오용방지 • 성능에대한고민제거 • 시스템확장시유리 public class Reserve { private Customer reserver; … public class Reserve { private CustomerId reserverId; … 47DDD 소개@서문래
  • 48.
    애그리거트 간 연관:ID 참조 • 연관된애그리거트와의조합이필요한기능 • 상태변경기능 • 응용서비스에서조합처리 • 도메인서비스고려 • 여러애그리거트함께조회 • 조회전용DAO고려 48DDD 소개@서문래
  • 49.
    사고 방식 추가 •조인,데이터애그리거트,기능 • 애그리거트단위로기능구현 • 기능에서객체의상태를변경 • 루트에서다른객체에위임 • 한개모델조회,명령 • CQRS • 조회전용DTO/DAO 49DDD 소개@서문래
  • 50.
    # 응용 서비스 50DDD소개@서문래
  • 51.
    응용 서비스 • 사용자가요청한기능을실행하고결과를리턴 •일종의입력과출력을가진프로시저 • 도메인을사용해서기능구현 • 도메인영역과표현영역을연결해주는창구 51DDD 소개@서문래
  • 52.
    응용 서비스의 구현 •도메인객체를사용한기능구현 • 리포지토리에서애그리거트루트를구하고 • 애그리거트루트의기능을실행 @Service public class ActivateOperatorService { private OperatorRepository operatorRepository; @Transactional public void activate(String operatorId) { // 애그리거트 루트를 구하고 Operator operator = operatorRepository.findOne(operatorId); if (operator == null) throw new OperatorNotFoundException(); // 도메인 기능을 실행 operator.activate(); } 52DDD 소개@서문래
  • 53.
    응용 서비스의 입력과출력 • 메서드파라미터 • 기능을수행하는데필요한데이터 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 소개@서문래
  • 54.
    응용 서비스의 입력과출력 • 리턴 • 요청처리결과를사용자에게보여줄경우응답제공 • 예,새로생성한주문의ID,주문목록 • 고민거리 • 도메인객체리턴vsUI에노출할데이터만담은객체리턴 • 구현편의성,팀표준,성능등고려 • 조회전용모델검토 • 익셉션은실패를의미 • 응용서비스나도메인에서발생 54DDD 소개@서문래
  • 55.
    응용 서비스와 트랜잭션 •트랜잭션담당 public class ChangePasswordService { @Transactional public void changePassword(ChangePasswordRequest request) { Member member = findExistingMember(request.getMemberId()); member.changePassword( request.getCurrentPassword(), request.getNewPassword()); } ... * 고민거리 : Open Session In View 55DDD 소개@서문래
  • 56.
    응용 서비스에 도메인로직 넣지 않기 • 도메인로직이나제약을구현하면안됨! public class ActivateOperatorService { private OperatorRepository operatorRepository; @Transactional public 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 소개@서문래
  • 57.
    몇 가지 고민할것 • 서비스 • 크기 • 도메인단위로기능몰아넣기vs기능군별로서비스구분 • 인터페이스필요여부 • 인터페이스 +구현클래스 • 그냥클래스만 • 서비스vs표현vs도메인 • 값검증 • 권한검사 57DDD 소개@서문래
  • 58.
    # BOUNDED CONTEXT 58DDD소개@서문래
  • 59.
    BOUNDED CONTEXT • 모델경계=언어경계 •문백안에서의미 • 논리적인언어경계선 • 기능을제공하는물리 적인시스템 • 조직구조를따라하위 도메인에매핑 59DDD 소개@서문래
  • 60.
  • 61.
    BC 간 관계 •서비스공급자,고객 • 상류,하류 • 공개호스트서비스 OpenHostService • 안티코럽션레이어 AnticorruptionLayer • 공유커널 SharedKernel • 독립방식 SeparateWay 61DDD 소개@서문래
  • 62.
  • 63.
  • 64.
    끝 최범균 | madvirus@madvirus.net| http://javacan.tistory.com 64DDD 소개@서문래