도메인구현 KSUG 20151128

78
몇 가지 도메인 구현 이야기 최범균 ([email protected]), 20151128

Transcript of 도메인구현 KSUG 20151128

Page 1: 도메인구현 KSUG 20151128

몇 가지도메인 구현 이야기

최범균([email protected]),   2015-­‐11-­‐28

Page 2: 도메인구현 KSUG 20151128

내용• DIP• AGGREGATE과 참조• CQRS• 이벤트

2

Page 3: 도메인구현 KSUG 20151128

#1, 아키텍처와 DIP

3

Page 4: 도메인구현 KSUG 20151128

흔한 구조

ReserveService JavaMailService

Reservation ReservationRepository

4

Page 5: 도메인구현 KSUG 20151128

인프라스트럭처

도메인impl패키지

응용

모듈 구조

ReserveService

JavaMailService

Reservation ReservationRepository

JpaReservationRepository

JPA

5

Page 6: 도메인구현 KSUG 20151128

인프라스트럭처

도메인impl패키지

응용

인프라로의 의존

ReserveService

JavaMailService

Reservation ReservationRepository

JpaReservationRepository

JPA

6

Page 7: 도메인구현 KSUG 20151128

인프라에 의존할 때의흔한 단점

• 구현교체의어려움

• 응용서비스나 도메인로직테스트어려움

7

Page 8: 도메인구현 KSUG 20151128

원인à고수준/저수준이 뒤섞임,즉, 저수준이 고수준에 영향을 줌

ReserveService:예약을 위한 응용 로직 à고수준Java API로 메일을 발송à 저수준

도메인:객체 영속성 à고수준JPA로 보관à 저수준RDBMS에 보관 à저저수준

• 고수준모듈 :  의미있는단일기능을제공하는모듈• 저수준모듈 :고수준모듈의기능을구현하기위해필요한하위기능의실제구현 8

Page 9: 도메인구현 KSUG 20151128

DEPENDENCY INVERSION PRINCIPLE

• 뒤섞임을 제거하려면à고수준입장에서 저수준 구현을추상화해서의존을뒤집음

ReserveService <<interface>>EmailNotifier

JavaMailEmailNotifier

응용 로직을 구성하는“이메일 통지하기"를 표현

9

ReserveService

JavaMailService

Page 10: 도메인구현 KSUG 20151128

DIP 적용예

“저수준의 상세한 내용 없이 구현하기” 참고, http://javacan.tistory.com10

Page 11: 도메인구현 KSUG 20151128

DIP?

도메인

impl패키지

SomeDomainService ReservationRepository

JpaReservationRepository

11

응용 ReserveService

인프라스트럭처

EmailService

JavaEmailService

Page 12: 도메인구현 KSUG 20151128

잘못된 DIP 적용

도메인

impl패키지

SomeDomainService ReservationRepository

JpaReservationRepository

12

응용 ReserveService

인프라스트럭처

EmailService

JavaEmailService

JPA

Page 13: 도메인구현 KSUG 20151128

올바른 DIP 적용

도메인 SomeDomainService ReservationRepository

13

응용 ReserveService

인프라스트럭처

EmailNotifier

JavaEmailNotifier

JpaReservationRepository

Page 14: 도메인구현 KSUG 20151128

DIP 적용아키텍처

ReserveService

Reservation ReservationRepository

JpaReservationRepository

EmailNotifier

JavaMailEmailNotifierController

MessageListener

14

Page 15: 도메인구현 KSUG 20151128

노력• 끊임없는 추상화연습• DIP  à고수준모듈입장에서추상화

15

Page 16: 도메인구현 KSUG 20151128

#2, AGGREGATE, 참조

16

Page 17: 도메인구현 KSUG 20151128

많은 객체

17

Page 18: 도메인구현 KSUG 20151128

많은 객체

18

복잡도관계? 이해?

작은 것에매몰

Page 19: 도메인구현 KSUG 20151128

상위 수준에서 묶어서 생각하면

19

Page 20: 도메인구현 KSUG 20151128

AGGREGATE = 개념적으로 하나인 객체군

<<ROOT>>Order OrderLineShippingInfo

Orderer

20

Page 21: 도메인구현 KSUG 20151128

AGGREGATE

• 관련된객체들의 묶음• 특징• 데이터변경시한단위로처리됨• 비슷한또는거의유사한라이프사이클을 갖는객체들• 특히,  삭제시함께삭제됨

• 경계를가짐• 기본적으로,  한Aggregate에 속한객체는다른Aggregate에

속하지않음• 필요성• 많은도메인모델을가능한간단하고이해가능한수준으

로만들필요• 연관의개수를줄임으로써복잡도감소

21

Page 22: 도메인구현 KSUG 20151128

AGGREGATE = 개념적으로 하나인 객체군

Order  order  =  orderRepository.findById(orderId);

order.changeShippingInfo(newShippingInfo);order.calcel();

완전체를 로딩

AGG  ROOT가로직/일관성(트랜잭션)처리  책임

22

Page 23: 도메인구현 KSUG 20151128

도메인 모델에서 AGG 찾기

Showing Reservation Customer

Movie DiscountStrategy Rule

SeatNo Grade

23

Page 24: 도메인구현 KSUG 20151128

요구 사항에 따라AGG 경계 정의예매시영화의시간과좌석을할당한다 회원은등급을갖는다

영화는 상영일정을갖는다?

영화별로가격할인규칙이다르다(영화별로다른가격할인을갖는다?)

24

Page 25: 도메인구현 KSUG 20151128

AGG 경계 : 규칙〮트랜잭션 범위

상영관리자가영화일정을추가해도영화정보는바뀌지않음

컨텐츠담당자가영화출연자정보를변경해도상영일정은바뀌지않음

동시성 처리를 할 때, Moive를 변경하는 동안 관련 Showing을 잠금 필요가 없음

Showing

Movie

25

Page 26: 도메인구현 KSUG 20151128

AGG 경계 : 규칙〮트랜잭션 범위

Movie DiscountStrategy Rule

• 영화 별로 할인 정책이 고정된다.• 할인 정책 별로 적용 가능 규칙을 갖는다.

Movie가AGG  루트:• movie.updateDescription(desc);• movie.changeDiscountStrategies(discountStrategies);• movie.calculateFee(showing);

26

Page 27: 도메인구현 KSUG 20151128

AGG 경계 : SRP

Movie DiscountStrategy Rule

• Movie는영화 정보 제공하는 책임만 갖도록 구성• 할인 계산은 별도 모듈로 분리

FeeCalculator

feeCalculator.calculate(movie,  showing)

27

Page 28: 도메인구현 KSUG 20151128

편리한 참조?

Showing Reservation Customer

Movie

reservation.getShowing().getTime()reservation.getShowing().getMovie().getTitle()reservation.getCustomer().getName()

28

Page 29: 도메인구현 KSUG 20151128

AGG 간참조의 잠재적 문제편한탐색을오용

(불필요한)고민

showing.getMovie().changeDescription(…);

showing.changeDiscountStrategy(disStrategy);

//  Showing.javapublic  void  changeDiscountStrategy(List<DiscountStrategy>   strategies)   {this.movie.changeDiscountStrategy(strategies);

}

Lazy  Loading  vs  Earger  Loading?  OSIV?

29

Page 30: 도메인구현 KSUG 20151128

AGG 간참조의 잠재적 문제확장 방해

Oracle

MySQL

Custom  Rule  Engine

30

Page 31: 도메인구현 KSUG 20151128

AGG 간 ID로 참조하기

public  class  Showing  {…private  MovieId movieId;…

}

public  class  Reservation  {…private  ShowingId showingId;private  CustomerId customerId;…

}

Showing Reservation Customer

31

Page 32: 도메인구현 KSUG 20151128

연관 객체조합은 응용 서비스에서 처리

public  class  ReserveService   {

public  ReservationId  reserve(ShowingId   showingId,  CustomerId  customerId)   {Showing  showing  =  showingRepository.findById(showingId);checkNotNull(shwoing);Movie  movie  =  movieRepository.findById(showing.getMovie());checkNotNull(movie);Customer  customer  =  customerRepository.findById(customerId);checkNotNull(customer);

Reservation  reservation  =  reservationFactory.create(showing,  customer,  movie.calculateFee(showing)

);return  reservation.getId();}…

} 32

Page 33: 도메인구현 KSUG 20151128

ID 참조의 특징• 복잡도낮춤• 모델복잡도• 구현복잡도

• Lazy  Loading효과

• 오용방지• 응집도강화

• AGG별로 확장가능

• 조회성능영향• 별도모듈

• 더많은코드

33

Page 34: 도메인구현 KSUG 20151128

노력• 인내필요• 당장바꾸기힘든DB  조인중심표준• 조인에집착하는개발자

• 기술을떠나구현할 수있는실력쌓기• 꼭ORM이있어야AGGREGATE를 구현할수있는것은아님• 기술중심이아닌도메인중심으로AGGREGATE를 생각

34

Page 35: 도메인구현 KSUG 20151128

#3, CQRS

35

Page 36: 도메인구현 KSUG 20151128

도메인 모델로 뷰처리${resList[0].customer.name}님의예매 목록:

<c:forEach  var='res'  items='${resList}'>예매번호 : ${res.number}영화 : <a  href="…?id=${res.showing.movie.id">

${res.showing.movie.title}</a>

시간 : ${res.showing.time}금액 : ${res.fee}

</c:forEach> 참조로 다 연결한 경우

List<Reservation>  resList    =reservationRepository.findByCustomer(customerId);

model.addAttribute("resList",   resList);

36

Page 37: 도메인구현 KSUG 20151128

복잡한 도메인의 조회기능 특징• 여러AGG에걸쳐데이터접근

Showing-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐id:  ShowingIdtime:  Time…

Reservation-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐

number:  ReservationNoshowing:   ShowingIdcustomer:   CustomerIdfee:  Money…

Customer-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐id:  CustomerIdname:  String…

Movie-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐id:  MovieIdtitle:   Stringdescription:   String…

${resList[0].customer.name}님의예매목록:

<c:forEach9var='res'9items='${resList}'>예매번호 : ${res.number}영화 : <a9href="…?id=${res.showing.movie.id">

${res.showing.movie.title}</a>

시간 : ${res.showing.time}금액 : ${res.fee}

</c:forEach>

37

Page 38: 도메인구현 KSUG 20151128

복잡한 도메인의 조회기능 특징• 성능중요• 최대한빨리사용자에게 화면을제공해야함

• 고민• 한방쿼리!• 레프트조인!• DB  전용기능!• …

38

Page 39: 도메인구현 KSUG 20151128

한모델로 처리하면

한방쿼리!레프트조인!DB  전용기능!…

같은연관에대해Lazy  LoadingEager  LoadingBatch  Loading…

도메인모델구현복잡도 증가

39

Page 40: 도메인구현 KSUG 20151128

상태 변경모델과 조회 모델분리

[출처 :  http://martinfowler.com/bliki/CQRS.html]

40

Page 41: 도메인구현 KSUG 20151128

조회 전용모델

ReservationList-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐

customerName:   Stringreservations:   List<ReservationData>…

ReservationData-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐

number:   StringmovieId:   StringmovieTitle:   StringshowingTime:   Timefee:  Money…

${resList.customerName}님의 예매목록:

<c:forEach   var='res'   items='${resList.reservations}'>예매번호 :${res.number}영화 :<a  href="…?id=${res.movieId">

${res.movieTitle}</a>

시간 :${res.showingTime}금액 :${res.fee}

</c:forEach>

41

Page 42: 도메인구현 KSUG 20151128

다른 기술, 다른아키텍처 사용

DAO(MyBatis)

컨트롤러

뷰 모델

컨트롤러

응용 서비스

도메인

인프라(JPA)

42

Page 43: 도메인구현 KSUG 20151128

다른 저장소 사용

DAO

컨트롤러컨트롤러

응용 서비스

도메인

인프라(JPA,  메시징)

43

Page 44: 도메인구현 KSUG 20151128

같은 기술, 뷰모델추가

컨트롤러

뷰 데이터 모델

컨트롤러

응용 서비스

도메인

인프라(JPA)

응용 서비스

44

Page 45: 도메인구현 KSUG 20151128

CQRS 이점• 모델복잡도감소

도메인자체에집중조회성능을위한코드없음

45

Page 46: 도메인구현 KSUG 20151128

CQRS 이점• 조회성능향상에유리

조회전용DB를사용한처리량향상

조회에특화된쿼리

조회단위캐시기술적용

46

Page 47: 도메인구현 KSUG 20151128

부담 요인• 더많은코드,  더많은기술

더 많은 코드

데이터 동기화 처리

더 많은 구현 기술

47

Page 48: 도메인구현 KSUG 20151128

노력• 장점과단점을 고려해서 CQRS  도입여부결정필요• 트래픽이없는데조회전용저장소고집하지말것• 트래픽이증가하면조회전용저장소도입검토

• 도메인이 복잡하면 효과있음• 더많은코드à더많은시간 :  X• 한모델로뷰처리비용>>  뷰모델로처리비용 :  O

48

Page 49: 도메인구현 KSUG 20151128

#4, 이벤트

49

Page 50: 도메인구현 KSUG 20151128

예매 취소시 환급하려면?

• 도메인모델에서 처리?

50

public  class  Reservation  {

public  void  cancel(RefundService   refundSvc)   {…  //취소처리

refundStatus  =  State.REFUND_STARTED;

try  {refundSvc.refund(this.paymentId);refundStatus  =  State.REFUND_COMPLETED;

}  catch(SomeException   ex)  {…

}}

}

Page 51: 도메인구현 KSUG 20151128

예매 취소시 환급하려면?

• 응용서비스에서 처리?

51

public  class  CancelReservation   {

@Transactionalpublic  void  cancel(ReservationId   resId)  {Reservation  res  =  findById(resId);res.cancel();

res.refundStarted();try  {refundSvc.refund(res.getPaymentId());res.refundCompleted();

}  catch(SomeException   ex)  {…  

}}

}

Page 52: 도메인구현 KSUG 20151128

고민거리 : 트랜잭션?

52

public   class  Reservation   {

public   void  cancel(RefundService   refundSvc)   {…  //취소처리

refundStatus   =  State.REFUND_STARTED;

try  {refundSvc.refund(this.paymentId);

refundStatus   =  State.REFUND_COMPLETED;}  catch(SomeException   ex)  {…

}}

}

public   class  CancelReservation   {

@Transactionalpublic   void  cancel(ReservationId   resId)   {Reservation   res  =  findById(resId);res.cancel();

res.refundStarted();try  {refundSvc.refund(res.getPaymentId());res.refundCompleted();

}  catch(SomeException   ex)  {…  

}}

}

외부 서비스가 정상이 아닐 경우,트랜잭션 처리는?

Page 53: 도메인구현 KSUG 20151128

고민거리 : 성능

53

public   class  Reservation   {

public   void  cancel(RefundService   refundSvc)   {…  //취소처리

refundStatus   =  State.REFUND_STARTED;

try  {refundSvc.refund(this.paymentId);

refundStatus   =  State.REFUND_COMPLETED;}  catch(SomeException   ex)  {…

}}

}

public   class  CancelReservation   {

@Transactionalpublic   void  cancel(ReservationId   resId)   {Reservation   res  =  findById(resId);res.cancel();

res.refundStarted();try  {refundSvc.refund(res.getPaymentId());res.refundCompleted();

}  catch(SomeException   ex)  {…  

}}

}

외부 서비스 완료까지쓰레드 블록킹

Page 54: 도메인구현 KSUG 20151128

고민거리 : 설계

54

public   class  Reservation   {

public   void  cancel(RefundService   refundSvc)   {…  //취소처리

refundStatus   =  State.REFUND_STARTED;

try  {refundSvc.refund(this.paymentId);

refundStatus   =  State.REFUND_COMPLETED;}  catch(SomeException   ex)  {…

}}

}

public   class  CancelReservation   {

@Transactionalpublic   void  cancel(ReservationId   resId)   {Reservation   res  =  findById(resId);res.cancel();

res.refundStarted();try  {refundSvc.refund(res.getPaymentId());res.refundCompleted();

}  catch(SomeException   ex)  {…  

}}

}

예매 도메인 모델에환불 도메인 로직이 섞임

Page 55: 도메인구현 KSUG 20151128

고민거리 : 설계

55

public   class  Reservation   {

public   void  cancel(RefundService   refundSvc)  {…  //취소처리

refundStatus   =  State.REFUND_STARTED;

try  {refundSvc.refund(this.paymentId);

refundStatus   =  State.REFUND_COMPLETED;}  catch(SomeException   ex)  {…

}}

}

public   class  CancelReservation   {

@Transactionalpublic   void  cancel(ReservationId   resId)   {Reservation   res  =  findById(resId);res.cancel();

res.refundStarted();try  {refundSvc.refund(res.getPaymentId());res.refundCompleted();

}  catch(SomeException   ex)  {…  

}}

}

기능이 추가되면 필요한 객체 파라미터 추가?DI로 주입 시도?

Page 56: 도메인구현 KSUG 20151128

적용해 볼만한 것

56

이벤트 + 비동기

Page 57: 도메인구현 KSUG 20151128

이벤트•과거에벌어진어떤것• 주로상태의변화

• 이벤트예• 영화추가함(NewMoviedCreated)• 예매함(RevervationCreated)• 영화상영일정추가함(ShowingAdded)• 영화정보변경함(MovieInfoChanged)• 영화가격정책변경함(PricePolicyUpdated)• 예매취소함(ReservationCanceled)

57

Page 58: 도메인구현 KSUG 20151128

이벤트의 구성• 구성• 발생주체(주로 식별자),  일렬번호/버전,  타입,  발생시간• 내용(payload)

• 예,  회원암호변경이벤트• 발생주체 :  "회원mad"• 버전 :  1• 타입 :  "PasswordChangedEvent"• 발생시간 :  2015-­‐11-­‐27   09:59:59• 내용 :  {id:"mad",  newPwd:  "xxxx"}

58

Page 59: 도메인구현 KSUG 20151128

이벤트 관련 구성요소

59

이벤트 생성 주체 이벤트 디스패처(이벤트 퍼블리셔)

이벤트 핸들러(이벤트 구독자)

이벤트

Page 60: 도메인구현 KSUG 20151128

이벤트 용도 1

• 트리거• 다른기능을수행하기위한트리거로이벤트를사용• 예시

• 예매를하면SMS로통지한다.• 예매함이벤트àSMS  통지트리거

• 예매를취소하면환불한다.• 예매취소이벤트à환불트리거

60

예매취소

이벤트디스패처

예매취소됨이벤트핸들러예매

취소됨이벤트

예매취소됨이벤트

환불처리

Page 61: 도메인구현 KSUG 20151128

이벤트 용도 2

• 데이터동기화• 다른시스템간데이터동기화목적으로이벤트사용• 예시

• 상영일정추가이벤트핸들러à조회전용저장소에 추가데이터반영

61

상영일정추가

이벤트디스패처

일정추가됨이벤트핸들러일정

추가됨이벤트

일정추가됨이벤트

일정데이터추가

커맨드 모델저장소

쿼리 모델저장소

Page 62: 도메인구현 KSUG 20151128

도메인과 이벤트• 도메인의 상태변경을이벤트로 표현• "~할때", "~가발생하면", "만약~하면"등의요구사항이실

제로상태변경인지확인• 예시

• "영화정보를변경할때"àMovieInfoChangedEvent• "예매를취소하면"àReservationCanceledEvent

62

Page 63: 도메인구현 KSUG 20151128

(도메인) 이벤트 발생과 처리

63

public  class  Reservation  {public  void  cancel()   {…취소로직Events.raise(new   ReservationCanceledEvent(getId(),   getPaymentNo(),   …));

}}

public  class  CancelReservationService   {@Transactionalpublic  void  cancel(ReservationId   resId)  {Events.register((ReservationCanceledEvent   evt)   -­‐>  refundSvc.refund(evt.getPaymentNo())

);Reservation  resv  =  findById(resId);resv.cancel();

}}

Page 64: 도메인구현 KSUG 20151128

이벤트 적용 장점• 서로다른도메인영역의 로직이섞이는것방지• 불필요한결합(coupling)  제거

64

예매!취소

이벤트!디스패처

예매!취소됨이벤트!핸들러예매!!

취소됨이벤트

예매!!취소됨이벤트

환불!처리

예매 취소에 더 이상 환불 로직 없음예매 도메인에서 환불 도메인으로의 의존 제거

Page 65: 도메인구현 KSUG 20151128

이벤트 적용 장점• 이벤트핸들러 추가로기능확장

65

예매취소

이벤트디스패처

예매취소됨이벤트핸들러예매

취소됨이벤트

예매취소됨이벤트

환불처리

예매취소됨이벤트핸들러2

이메일통지예매

취소됨이벤트

Page 66: 도메인구현 KSUG 20151128

동기 이벤트 처리의 단점• 트랜잭션 처리문제,성능(처리량) 문제

66

public  class  ReservationService   {@Transactionalpublic  void  reserve(MovieId   movieId,  ShowingId  showingId,  CustomerId  custId)  {

Events.register((ReservationCreatedEvent   evt)  -­‐>  emailNotifier.notify(…)

);

…//  예약도메인로직에서이벤트발생

}}

이메일 발송 중 익셉션이 발생하면?

이메일 서버가 응답 시간이 길면?

Page 67: 도메인구현 KSUG 20151128

비동기 : 이벤트 처리 시간• "A할 때,  B해라"요구사항에서 A와B의간격• 도메인전문가의 '바로'는 '즉시실행'이아님

• 수용가능한지연허용범위가있음

• 예시• 예매취소시, (늦어도 30분이내에)환급처리함• 결제완료후, (늦어도다음날7시부터)배송상태를조회

할수있어야함• 티켓예매시, (최대10분안에)분단위티켓판매 통계에반

영함• 분단위티켓판매통계가10분주기로생성되어도 대세지장없음

67

Page 68: 도메인구현 KSUG 20151128

비동기 : 이벤트와 데이터일관성• "A할 때, B해라"요구사항에서 A와B의일관성• 실제로는한트랜잭션이 아닌경우많음

• 예시• 이메일발송실패한다고 회원가입을못하지않음• 고객이예매취소하면,운영자가수동으로환불처리가능

68

Page 69: 도메인구현 KSUG 20151128

응용/인프라

비동기 이벤트 처리 방식 1

• 이벤트디스패처에서 메시지 큐에전송

69

이벤트디스패처 메시지큐도메인 메시지

리스너

스토리지

도입시 고려사항• 글로벌 트랜잭션• 메시지큐가 비정상일 때 이벤트 재전송 방안

이벤트핸들러

Page 70: 도메인구현 KSUG 20151128

비동기 이벤트 처리 방식 2

• 로컬이벤트핸들러가DB에 저장• 포워더가 이벤트를 전달

70

응용/인프라

이벤트디스패처

로컬핸들러도메인

메시지리스너

스토리지

메시지큐포워더

단일 트랜잭션으로처리이벤트

저장

이벤트를 주기적으로 읽어와 전달어디까지 전달했는지 추적

Page 71: 도메인구현 KSUG 20151128

DB 저장과 포워더 구현예• 쇼핑몰, ERP,택배사연동

71

쇼핑도메인

쇼핑+이벤트DB

포워더

주문/결제시관련 이벤트를한 트랜잭션으로 저장

ERP연동모듈

택배사연동모듈

1분 주기로 새로전달할 이벤트를읽어와 외부 시스템에전달

Page 72: 도메인구현 KSUG 20151128

비동기 이벤트 처리 방식 3

• 로컬 이벤트핸들러가DB에 저장• 이벤트수신측에서 이벤트를 직접가져감(pull)• 이벤트제공API이용 (RESTful  API등)

72

응용/인프라

이벤트디스패처

로컬핸들러도메인

스토리지

이벤트저장

이벤트(REST)  API

이벤트Fetcher

이벤트핸들러

단일 트랜잭션으로 처리

읽어올 이벤트 범위를지정해서 가져와 핸들러에 전달

이벤트 데이터 제공함도메인과 같은 프로세스에서실행해도 무방

Page 73: 도메인구현 KSUG 20151128

비동기 이벤트의 특징• 이벤트생성자와 핸들러의트랜잭션 분리• 처리량향상• 고객에게빠른응답

• 구조가복잡해짐• 재발송,재처리등고려사항증가

73

Page 74: 도메인구현 KSUG 20151128

이벤트 도입시 고려사항• 이벤트도착순서

• 이벤트발송실패시 재발송

• 이벤트재처리

74

Page 75: 도메인구현 KSUG 20151128

노력• 사고전환• 상태의변경==이벤트• 순차실행à이벤트+핸들러• 트랜잭션일관성 :비동기처리+Eventually  일관성

• 기술집착버림• 이벤트,비동기자체에대한이해필요• 메시지큐가있어보이지만,간단한기술로도충분히구현

가능• 이벤트+  CQRS

75

Page 76: 도메인구현 KSUG 20151128

#5, 정리

76

Page 77: 도메인구현 KSUG 20151128

내용• DIP• AGGREGATE• CQRS• 이벤트

77

Page 78: 도메인구현 KSUG 20151128

78

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