Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
이벤트 소싱(Event Sourcing)
학습 내용 공유
신림프로그래머, 최범균, 2015-03-21
madvirus@madvirus.net
다룰 내용
•  이벤트와 이벤트 소싱
•  이벤트 소싱을 이용한 도메인 구현
•  성능
•  정리
2	
  
지난 70일간의 몸무게 증감 기록
•  전날과의 무게 차이 기록
-­‐2.5	
  
-­‐2	
  
-­‐1.5	
  
-­‐1	
  
-­‐0.5	
  
0	
  
0.5	
  
1	
  
1.5	
  
3	
  
최종 무게 변화량?
•  증감을 모두 더해서 최종 변화량 구함
4	
  
(-0.5) + 0.2 + 0 + (-0.6) + (-0.2) +
0.2 + 0 + 0.2 + 0.2 + 0.3 +
....
....
(-0.8)...
최종 무게 변화량?
•  증감을 모두 더해서 최종 변화량 구함
5	
  
당일공개
(-0.5) + 0.2 + 0 + (-0.6) + (-0.2) +
0.2 + 0 + 0.2 + 0.2 + 0.3 +
....
....
(...
-­‐3	
  
-­‐2	
  
-­‐1	
  
0	
  
1	
  
2	
  
이벤트
•  과거에 벌어진 어떤 것을 이벤트로 정의
– 주로 상태의 변화
6	
  
몸무게가 1Kg 줄었음
몸무게가 1Kg 늘었음
이벤트의 구성
•  구성
– 생성자, 일렬번호/버전, 타입, 발생 시간
– 내용(payload)
•  예, 회원 암호 변경 이벤트
– 생성자: "회원mad"
– 버전: "1"
– 타입: "PasswordChangedEv...
이벤트 소싱(Event Sourcing)
8	
  
* http://martinfowler.com/eaaDev/EventSourcing.html
어플리케이션의모든상태변화를
순서에따라이벤트로보관한다.
Captureallc...
이벤트 소싱과 현재 상태
9	
  
-0.3kg 몸무게 변했음 이벤트
-0.6kg 몸무게 변했음 이벤트
0.4kg 몸무게 변했음 이벤트
-1.2kg 몸무게 변했음 이벤트
...
-0.1kg 몸무게 변했음 이벤트
-0.5...
10	
  
이벤트 소싱과 도메인 구현
도메인의 기능
•  암호 변경 예: 엔티티 로딩+로직+상태 변경
– 엔티티(객체 또는 데이터) 로딩
•  로직 수행에 필요한 엔티티 객체(또는 데이터) 로딩
– 로직
•  oldPw가 현재 암호와 일치하는지 검사
•  ...
도메인 구현 : SQL(데이터) 중심
12	
  
-- 서비스: 흐름 처리, 도메인 로직 수행
public void changePw(ChangePwCmdcmd) {
MemberDto m =
dao.selectById(c...
도메인 구현
이벤트 소싱 전 - ORM
13	
  
-- 서비스: 도메인 객체 이용 흐름 처리
public void changePw(ChangePwCmd cmd) {
Member m = repo.findById(cmd....
도메인 구현 이벤트 소싱 적용
•  변화되는 것
14	
  
영역 적용 전 (RDBMS/ORM) 적용 후
도메인 객체 로딩 SQL : SELECT 쿼리
ORM : 프레임워크가 매핑 설정
을 이용해서 SELECT 쿼리 실...
도메인 객체 로딩 1
•  이벤트 목록 발생 순서대로 저장되어 있다면
15	
  
회원 ID "mad"와 관련된 이벤트
MemberCreatedEvent("mad", "bk", "a@a.com", "pw")
EmailV...
도메인 객체 로딩 2
•  이벤트를 로딩해 도메인 객체에 적용
– 도메인 객체의 이벤트 핸들러를 실행할 때, 인자
로 이벤트 객체 전달
16	
  
MemberCreatedEvent("mad","bk","a@a.com"...
도메인 객체 로딩 3
•  도메인 객체의 이벤트 핸들러 메서드
17	
  
mem = new Member(); Member 클래스의 이벤트 핸들러 메서드
mem.on(memCreatedEvent)
on(MemberCre...
도메인 객체 로딩 4
18	
  
Member m = memberRepository.load("mad");
MemberCreatedEvent("mad","bk","a@a.com","pw")
EmailVerifiedEve...
도메인 객체 기능의 변화 1
•  도메인 로직 수행 + 이벤트 생성
19	
  
-- Member 클래스
public PasswordChangedEvent changePassword(
String oldPw, Strin...
상태 변경 이벤트 생성 및 보관	
  상태를 변경시키는 기능	
  
도메인 객체 기능의 변화 2
•  상태를 변경시키는 모든 도메인 기능은 알맞
은 이벤트를 발생시킴!
20	
  
new Member(id,name,em...
어플리케이션 서비스의 변화
•  도메인 객체가 발생한 이벤트를 저장
21	
  
public class ChangePasswordService {
public void changePassword(ChangePasswor...
이벤트 보관
•  물리적인 저장소에 보관 필요
– RDBMS, NoSQL, 파일 등에 보관
•  도메인 객체 별 이벤트 목록 관리
– 이벤트의 발생 순서 유지
22	
  
회원1 관련 이벤트
version: 1, typ...
23	
  
성능
도메인 이벤트가 쌓이면?
24	
  
회원1 관련 이벤트
version: 1, type: MemberCreatedEvent
values: {id: "1", name: "...", ....}
version: 2, type...
스냅샷snapshot으로 로딩 속도 향상
25	
  
회원1 관련 이벤트
version: 1, type: MemberCreatedEvent
values: {id: "1", name: "...", ....}
version...
여러 도메인 객체에 대한 조회는?
•  다음을 이벤트 소싱으로 처리하려면?
– 100만 회원 대상, 최근 1주일 가입 신청자 중
아직 이메일 인증을 하지 않은 회원 찾기
26	
  
1번 객체
로딩Ÿ검사
2번 객체
로...
조회 전용 모델로 조회 속도 향상
•  시스템을 다음의 두 가지로 분리
– 기능 실행(Command) / 상태 조회(Query)
•  CQRS(Command Query Responsibility Segregation)
...
뷰 전용 모델	
  
조회 전용 모델 처리 예
28	
  
MemberCreated
Event
EmailVerified
Event
insert into NOVERIEDMEM
values (....)
Event
Store...
29	
  
기능 변경의 유연함
주문 도메인 취소 예
•  이벤트 소싱 기반 Order 도메인 객체
30	
  
public class Order {
private OrderState orderState;
private Payment payment;
...
취소 시간을 추가하려면?
•  이벤트 소싱 기반 Order 도메인 객체
31	
  
public class Order {
private OrderState orderState;
private Payment payment...
만약 RDBMS/SQL 기반이었다면?
32
alter table ORDER add column CANCELED_TIME timestamp
pstmt = conn.prepareStatemet("update ORDER se...
주문 금액을 long에서 Money로 바꾸면?
•  이벤트 소싱 기반 Order 도메인 객체
33	
  
public class Order {
private Money totalAmount; // 기존 long tota...
만약 RDBMS/SQL 기반이었다면?
34
alter table ORDER add column TOTAL_AMT2 double;
alter table ORDER add column TOTAL_AMT2_CURRENCY v...
35	
  
정리
장점
•  DB에 의존적이지 않은 도메인 코드 구현
–  테이블이나 ORM 기술의 제한/제약에서 벗어남
•  기능 변경
–  하위 호환 처리가 상대적으로 쉬움
–  이벤트로부터 완전히 새로운 도메인 객체의 생성도 가능
...
단점
•  익숙하지 않음
–  SQL 위주(데이터 중심) 개발 성향인 경우 적응 힘듬
•  단순 모델에는 적합하지 않음
–  단순 모델에 적용하기엔 구현이 복잡해짐
•  도구 부족
–  이벤트 소싱과 CQRS 지원 프레...
참고자료
•  Event Sourcing Basics :
http://docs.geteventstore.com/introduction/event-sourcing-basics/
•  CQRS : http://martinf...
39	
  
끝
Upcoming SlideShare
Loading in …5
×

Event source 학습 내용 공유

5,082 views

Published on

신림프로그래머 공개 모임에서 사용할 이벤트 소싱 발표 자료입니다. 이벤트, 이벤트 소싱을 이용한 도메인 구현 등에 대해 학습한 내용을 공유합니다.

Published in: Technology
  • Be the first to comment

Event source 학습 내용 공유

  1. 1. 이벤트 소싱(Event Sourcing) 학습 내용 공유 신림프로그래머, 최범균, 2015-03-21 madvirus@madvirus.net
  2. 2. 다룰 내용 •  이벤트와 이벤트 소싱 •  이벤트 소싱을 이용한 도메인 구현 •  성능 •  정리 2  
  3. 3. 지난 70일간의 몸무게 증감 기록 •  전날과의 무게 차이 기록 -­‐2.5   -­‐2   -­‐1.5   -­‐1   -­‐0.5   0   0.5   1   1.5   3  
  4. 4. 최종 무게 변화량? •  증감을 모두 더해서 최종 변화량 구함 4   (-0.5) + 0.2 + 0 + (-0.6) + (-0.2) + 0.2 + 0 + 0.2 + 0.2 + 0.3 + .... .... (-0.8) + (-1.5) + (-0.1) + 0.7 + 0.5 + 0.2 + 0 + (-0.6) + (-0.5) + 0.4
  5. 5. 최종 무게 변화량? •  증감을 모두 더해서 최종 변화량 구함 5   당일공개 (-0.5) + 0.2 + 0 + (-0.6) + (-0.2) + 0.2 + 0 + 0.2 + 0.2 + 0.3 + .... .... (-0.8) + (-1.5) + (-0.1) + 0.7 + 0.5 + 0.2 + 0 + (-0.6) + (-0.5) + 0.4
  6. 6. -­‐3   -­‐2   -­‐1   0   1   2   이벤트 •  과거에 벌어진 어떤 것을 이벤트로 정의 – 주로 상태의 변화 6   몸무게가 1Kg 줄었음 몸무게가 1Kg 늘었음
  7. 7. 이벤트의 구성 •  구성 – 생성자, 일렬번호/버전, 타입, 발생 시간 – 내용(payload) •  예, 회원 암호 변경 이벤트 – 생성자: "회원mad" – 버전: "1" – 타입: "PasswordChangedEvent" – 발생시간: 2015-03-21 09:59:59 – 내용: {id:"mad", newPwd: "xxxx"} 7
  8. 8. 이벤트 소싱(Event Sourcing) 8   * http://martinfowler.com/eaaDev/EventSourcing.html 어플리케이션의모든상태변화를 순서에따라이벤트로보관한다. Captureallchangestoanapplicationstate asasequenceofevents.
  9. 9. 이벤트 소싱과 현재 상태 9   -0.3kg 몸무게 변했음 이벤트 -0.6kg 몸무게 변했음 이벤트 0.4kg 몸무게 변했음 이벤트 -1.2kg 몸무게 변했음 이벤트 ... -0.1kg 몸무게 변했음 이벤트 -0.5kg 몸무게 변했음 이벤트 1.2kg 몸무게 변했음 이벤트 -0.8kg 몸무게 변했음 이벤트 0.2kg 몸무게 변했음 이벤트 발생 순서대로 최초 이벤트부터 마지막 이벤트까지 차례대로 재현하면 현재(최종) 상태가 됨! 이벤트 저장소(Event Store)
  10. 10. 10   이벤트 소싱과 도메인 구현
  11. 11. 도메인의 기능 •  암호 변경 예: 엔티티 로딩+로직+상태 변경 – 엔티티(객체 또는 데이터) 로딩 •  로직 수행에 필요한 엔티티 객체(또는 데이터) 로딩 – 로직 •  oldPw가 현재 암호와 일치하는지 검사 •  일치하지 않으면, 암호 변경 실패(예, 익셉션 발생) – 상태 변경 •  암호를 newPw로 변경함 11
  12. 12. 도메인 구현 : SQL(데이터) 중심 12   -- 서비스: 흐름 처리, 도메인 로직 수행 public void changePw(ChangePwCmdcmd) { MemberDto m = dao.selectById(cmd.getId()); if (m == null) throw new NoMemException(); if (!m.getPwd().equals(cmd.getOldPw())) throw new BadPwException(); m.setPwd(cmd.getNewPw()); dao.updatePw(m); } -- DAO: 쿼리 실행 public MemberDto selectById(String id) { pstmt = conn.prepareStatement( "select * from member where mem_id=?"); pstmt.setString(1, id); ResultSet rs = pstmt.executeQuery(); if (rs.next()) { MemberDto dto = new MemberDto(); dto.setId(rs.getString("mem_id")); dto.setPwd(rs.getString("pwd")); return dto; } ...close } public void updatePw(MemberDto dto) { pstmt = conn.prepareStatement( "update member set pwd = ? " + "where mem_id = ?" pstmt.setString(1, dto.getPwd()); pstmt.setString(2, dto.getId()); pstmt.executeUpdate(); ... } -- DTO: 데이터 보관 private String id; private String pwd; public String getPassword() { return pwd; } public void setPassword(String pwd) { this.pwd = pwd; } ...get/set 상태 변경  
  13. 13. 도메인 구현 이벤트 소싱 전 - ORM 13   -- 서비스: 도메인 객체 이용 흐름 처리 public void changePw(ChangePwCmd cmd) { Member m = repo.findById(cmd.getId()); if (m == null) throw new NoMemException(); m.changePw(cmd.getOldPw(), cmd.getNewPw()); } -- Member 엔티티: -- 매핑 설정, 도메인 기능, 상태 변경 @Id @Column("mem_id") private String id; @Column("pwd") private String password; public void changePw( String oldPw, Stirng newPw) { if (!this.password.equals(oldPw)) throw new BadPwException(); this.password = newPw; } -- 리포지토리: 도메인 객체 로딩 public Member findById(String id) { // ... ORM 관련 코드 return entityMgr.find(Member.class, id); } 상태 변경  
  14. 14. 도메인 구현 이벤트 소싱 적용 •  변화되는 것 14   영역 적용 전 (RDBMS/ORM) 적용 후 도메인 객체 로딩 SQL : SELECT 쿼리 ORM : 프레임워크가 매핑 설정 을 이용해서 SELECT 쿼리 실행 -  이벤트로부터 도메인 객체 생성 -  도메인 객체의 이벤트 핸들러를 이용해 상태 변경 반영 도메인 기능 SQL : 서비스 클래스 ORM : (일부) 엔티티 클래스 -  도메인 객체가 수행 -  상태 변경을 위한 이벤트 생성 상태 변경 반영 (데이터 변경) SQL : Insert/Update/Delete 쿼리 ORM : 엔티티 프로퍼티를 변경 하면 ORM 프레임워크가 알맞 은 쿼리 실행 -  도메인 객체가 발생한 이벤트를 저장소에 보관
  15. 15. 도메인 객체 로딩 1 •  이벤트 목록 발생 순서대로 저장되어 있다면 15   회원 ID "mad"와 관련된 이벤트 MemberCreatedEvent("mad", "bk", "a@a.com", "pw") EmailVerifiedEvent("mad") MemberEnabledEvent("mad") PasswordChangedEvent("mad", "newPw") MemberDisabledEvent("mad", "도용의심")
  16. 16. 도메인 객체 로딩 2 •  이벤트를 로딩해 도메인 객체에 적용 – 도메인 객체의 이벤트 핸들러를 실행할 때, 인자 로 이벤트 객체 전달 16   MemberCreatedEvent("mad","bk","a@a.com","pw") EmailVerifiedEvent("mad") MemberEnabledEvent("mad") PasswordChangedEvent("mad","newPw") MemberDisabledEvent("mad","도용의심") -- MemberRepository public Member load(String id) { List<Event> events = eventStore.load(id); if (events.isEmpty()) return null; Member mem = new Member(); for (Event evt : events) { mem.on(evt); // 이벤트 핸들러 } return mem; }
  17. 17. 도메인 객체 로딩 3 •  도메인 객체의 이벤트 핸들러 메서드 17   mem = new Member(); Member 클래스의 이벤트 핸들러 메서드 mem.on(memCreatedEvent) on(MemberCreatedEvent e) { this.id = e.getId(); this.name = e.getName(); this.email = e.getEmail(); this.password = e.getPassword(); this.emailValid = false; this.enabled = false; } mem.on(emailVerifiedEvent) mem.on(memEnabledEvent) on(EmailverifiedEvent e) { this.emailVaild = true; } on(MemberEnabledEvent e) { this.enabled = true; } mem.on(pwdChangedEvent) on(PasswordChangedEvent e) { this.password = e.getNewPassword(); } mem.on(memDisabledEvent) on(MemberDisabledEvent e) { this.enabled = false; } 이 벤 트   순 차   적 용 최신   상태  
  18. 18. 도메인 객체 로딩 4 18   Member m = memberRepository.load("mad"); MemberCreatedEvent("mad","bk","a@a.com","pw") EmailVerifiedEvent("mad") MemberEnabledEvent("mad") PasswordChangedEvent("mad","newPw") MemberDisabledEvent("mad","도용의심") m 객체 id = "mad" name = "bk" email = "a@a.com" password = "pw" validEmail = true enabled = false // load() 메서드 Member mem = new Member(); for (Event evt : events) { mem.on(evt); } 최신상태  
  19. 19. 도메인 객체 기능의 변화 1 •  도메인 로직 수행 + 이벤트 생성 19   -- Member 클래스 public PasswordChangedEvent changePassword( String oldPw, String newPw) { if (!this.password.equals(oldPw)) { throw new IdPasswordNotMatchingException(); } // this.password = newPw; 상태 변경하지 않음 // 변경할 상태 정보를 담은 이벤트 생성 return new PasswordChangedEvent(this.id, newPw); } public void on(PasswordChangedEvent e) { this.password = e.getNewPassword(); } 상태는 이벤트 핸들러에서만 변경   도메인 기능 메서드는 상태를 변경하는 대신, 변경할 정보를 담은 이벤트 생성  
  20. 20. 상태 변경 이벤트 생성 및 보관  상태를 변경시키는 기능   도메인 객체 기능의 변화 2 •  상태를 변경시키는 모든 도메인 기능은 알맞 은 이벤트를 발생시킴! 20   new Member(id,name,email,pw,time) à MemberCreatedEvent(id,name,email,pw,time) mem.verifyEmail(key) à EmailVerifiedEvent(id) MemberEnabledEvent(id) mem.changePassword(op, np) à PasswordChangedEvent(id, np) mem.disable(reason) à MemberDisabledEvent(id, cause)
  21. 21. 어플리케이션 서비스의 변화 •  도메인 객체가 발생한 이벤트를 저장 21   public class ChangePasswordService { public void changePassword(ChangePasswordCommand cmd) { Member mem = memRepository.load(cmd.getMemberId()); PasswordChangedEvent evt = mem.changePassword(cmd.getOldPw(), cmd.getNewPw()); eventStore.save(evt); mem.on(evt); } ... } 1. 도메인 객체 로딩 2. 도메인 객체 기능 실행 , 결과로 이벤트 생성 3. 이벤트 저장 4. 도메인 객체의 상태 변경
  22. 22. 이벤트 보관 •  물리적인 저장소에 보관 필요 – RDBMS, NoSQL, 파일 등에 보관 •  도메인 객체 별 이벤트 목록 관리 – 이벤트의 발생 순서 유지 22   회원1 관련 이벤트 version: 1, type: MemberCreatedEvent values: {id: "1", name: "...", ....} version: 2, type: EmailVerifiedEvent values: {id: "1"} version: 3, type: MemberEnabledEvent values: {id: "1"} 회원2 관련 이벤트 version: 1, type: MemberCreatedEvent values: {id: "2", name: "...", ....} version: 2, type: EmailVerifiedEvent values: {id: "2"} version: 3, type: MemberEnabledEvent values: {id: "2"} version: 4, type: PasswordChangedEvent values: {id: "2", newPw: "newpw"}
  23. 23. 23   성능
  24. 24. 도메인 이벤트가 쌓이면? 24   회원1 관련 이벤트 version: 1, type: MemberCreatedEvent values: {id: "1", name: "...", ....} version: 2, type: EmailVerifiedEvent values: {id: "1"} version: 3, type: MemberEnabledEvent values: {id: "1"} version: 1,000, type: SomeEvent values: {id: "1", ....} .........   한 도메인 객체의 최종 상태를 구하기 위해 한번에 1,000개의 이벤트를 로딩한다면, 전반적인 응답 속도 저하
  25. 25. 스냅샷snapshot으로 로딩 속도 향상 25   회원1 관련 이벤트 version: 1, type: MemberCreatedEvent values: {id: "1", name: "...", ....} version: 2, type: EmailVerifiedEvent values: {id: "1"} version: 999, type: MemberEnabledEvent values: {id: "1"} version: 1,000, type: SomeEvent values: {id: "1", ....} .........   특정 버전을 기준으로 이전 이벤트를 누적한 스냅샷 생성 스냅샷 id: 1, version: 999 스냅샷을 기준으로 이후 버전만 로딩
  26. 26. 여러 도메인 객체에 대한 조회는? •  다음을 이벤트 소싱으로 처리하려면? – 100만 회원 대상, 최근 1주일 가입 신청자 중 아직 이메일 인증을 하지 않은 회원 찾기 26   1번 객체 로딩Ÿ검사 2번 객체 로딩Ÿ검사 100만번 객체 로딩Ÿ검사 3번 객체 로딩Ÿ검사 ................   겁나게 느린 조회 응답 속도!
  27. 27. 조회 전용 모델로 조회 속도 향상 •  시스템을 다음의 두 가지로 분리 – 기능 실행(Command) / 상태 조회(Query) •  CQRS(Command Query Responsibility Segregation) 27   이벤트 소싱 기반 도메인 모델 데이터 기반 조회 모델 구현 이벤트 스토어 데이터 저장소 UI   상태를 변경하는 명령은 이벤트 소싱 기반 모델에 전달 상태 조회 요청은 데이터 기반 모델에 전달 이벤트를 전달해서 조회에 맞는 모델 생성
  28. 28. 뷰 전용 모델   조회 전용 모델 처리 예 28   MemberCreated Event EmailVerified Event insert into NOVERIEDMEM values (....) Event Store delete from NOVERIEDMEM where ... RDBMS UI   데이터 조회  
  29. 29. 29   기능 변경의 유연함
  30. 30. 주문 도메인 취소 예 •  이벤트 소싱 기반 Order 도메인 객체 30   public class Order { private OrderState orderState; private Payment payment; ... public List<Event> cancel() { if (!orderState.canCancel()) throw new CanNotCancelException(orderState); Event refundedEvent = payment.refund(); return Arrays.asList(refundedEvent, new CanceledEvent()); } public void on(RefundedEvent evt) { payment.on(evt); } public void on(CanceledEvent evt) { orderState = OrderState.CANCELD; } }
  31. 31. 취소 시간을 추가하려면? •  이벤트 소싱 기반 Order 도메인 객체 31   public class Order { private OrderState orderState; private Payment payment; private Date canceledTime; ... public List<Event> cancel() { if (!orderState.canCancel()) throw new CanNotCancelException(orderState); Event refundedEvent = payment.refund(); return Arrays.asList(refundedEvent, new CanceledEvent()); } … public void on(CanceledEvent evt) { orderState = OrderState.cancelState(); canceledTime = evt.getOccuredTime(); // 수정전발생도메인객체에도기능적용 } } 추가한 코드
  32. 32. 만약 RDBMS/SQL 기반이었다면? 32 alter table ORDER add column CANCELED_TIME timestamp pstmt = conn.prepareStatemet("update ORDER set state = 'CANCELED', " + "CANCELED_TIME = ? where ORDER_ID = ?"); … pstmt.setTimestamp(1, new Timestamp(orderDto.getCanceledTime())); … pstmt.executeUpdate(); pstmt = conn.prepareStatement("select * from ORDER where ORDER_ID = ?"); … rs = pstmt.executeQuery(); OrderDto dto = …; dto.setCanceledTime(rs.getTimestamp("CANCELED_TIME")); … public class OrderDto { … private Date canceledTime; …get/set 추가 } update ORDER set CANCELED_TIME = 어떻게든구해서설정where ORDER_ID = ?
  33. 33. 주문 금액을 long에서 Money로 바꾸면? •  이벤트 소싱 기반 Order 도메인 객체 33   public class Order { private Money totalAmount; // 기존 long totalAmount ... public OrderPlacedEvent2 place() { … // 기존: return new OrderPlacedEvent(…, totalAmt); // 새로운 타입 이벤트 생성으로 변경 return new OrderPlacedEvent2(…., Money.won(totalAmt)); } public void on(OrderPlacedEvent2 evt) { … totalAmount = evt.getTotalAmount(); } public void on(OrderPlacedEvent evt) { … // 기존에 이미 생성한 이벤트 반영 (호환성유지) totalAmount = Money.won(evt.getTotalAmount()); } } public class Money { private BigDecimal value; private Currency currency; public static Money won(long value) { return new Money(value, Currency.WON); } … } 새로운 이벤트 타입으로   기존 이벤트 데이터 영향없이 구현 변경 기존 이벤트에 대한   어렵지 않은 호환성 처리
  34. 34. 만약 RDBMS/SQL 기반이었다면? 34 alter table ORDER add column TOTAL_AMT2 double; alter table ORDER add column TOTAL_AMT2_CURRENCY varchar(3); update ORDER set TOTAL_AMT2 = TOTAL_AMT, TOTAL_AMT2_CURRENCY='WON'; alter table ORDER drop column TOTAL_AMT; -- 용감한 선택! pstmt = conn.prepareStatement("select * from ORDER where ORDER_ID = ?"); … rs = pstmt.executeQuery(); OrderDto dto = …; Money totalAmt = new Money(rs.getDouble("TOTAL_AMT2"), Current.of(rs.getString("TOTAL_AMT2_CURRENCY"))); dto.setTotalAmt(totalAmt); … public class OrderDto { private Money totalAmt; … } pstmt = conn.prepareStatement("insert into ORDER values (?, ?, …, ?, ? "); … pstmt.setDouble(10, orderDto.getMoney().getValue()); pstmt.setString(11, orderDto.getMoney().getCurrency().toString()); …
  35. 35. 35   정리
  36. 36. 장점 •  DB에 의존적이지 않은 도메인 코드 구현 –  테이블이나 ORM 기술의 제한/제약에서 벗어남 •  기능 변경 –  하위 호환 처리가 상대적으로 쉬움 –  이벤트로부터 완전히 새로운 도메인 객체의 생성도 가능 •  버그 추적 용이 –  이벤트를 차례대로 검사하면서 버그 원인 추적 가능 •  객체 지향/DDD와 좋은 궁합 –  복잡한 도메인을 객체 지향적으로 구현하기에 좋음 •  CQRS와 좋은 궁합 –  조회 관련 코드를 도메인에서 분리 –  조회 모델 분리로 조회 성능 향상 가능 36  
  37. 37. 단점 •  익숙하지 않음 –  SQL 위주(데이터 중심) 개발 성향인 경우 적응 힘듬 •  단순 모델에는 적합하지 않음 –  단순 모델에 적용하기엔 구현이 복잡해짐 •  도구 부족 –  이벤트 소싱과 CQRS 지원 프레임워크 부족 •  운영시 어려움 –  이벤트 데이터만으로는 최신 상태의 빠른 확인 불가 •  CQRS 필수! 37  
  38. 38. 참고자료 •  Event Sourcing Basics : http://docs.geteventstore.com/introduction/event-sourcing-basics/ •  CQRS : http://martinfowler.com/bliki/CQRS.html •  관련 도구 – Axon Framework : http://www.axonframework.org/ – EventStore : http://www.geteventstore.com – Akka Persistence(실험 버전) : http://doc.akka.io/docs/akka/snapshot/scala/persistence.html •  학습하면서 연습한 코드 : https://github.com/madvirus/evaluation 38  
  39. 39. 39   끝

×