SlideShare a Scribd company logo
1 of 39
Download to read offline
이벤트 소싱(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) + (-1.5) + (-0.1) + 0.7 + 0.5 +
0.2 + 0 + (-0.6) + (-0.5) + 0.4
최종 무게 변화량?
•  증감을 모두 더해서 최종 변화량 구함
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
-­‐3	
  
-­‐2	
  
-­‐1	
  
0	
  
1	
  
2	
  
이벤트
•  과거에 벌어진 어떤 것을 이벤트로 정의
– 주로 상태의 변화
6	
  
몸무게가 1Kg 줄었음
몸무게가 1Kg 늘었음
이벤트의 구성
•  구성
– 생성자, 일렬번호/버전, 타입, 발생 시간
– 내용(payload)
•  예, 회원 암호 변경 이벤트
– 생성자: "회원mad"
– 버전: "1"
– 타입: "PasswordChangedEvent"
– 발생시간: 2015-03-21 09:59:59
– 내용: {id:"mad", newPwd: "xxxx"}
7
이벤트 소싱(Event Sourcing)
8	
  
* http://martinfowler.com/eaaDev/EventSourcing.html
어플리케이션의모든상태변화를
순서에따라이벤트로보관한다.
Captureallchangestoanapplicationstate
asasequenceofevents.
이벤트 소싱과 현재 상태
9	
  
-0.3kg 몸무게 변했음 이벤트
-0.6kg 몸무게 변했음 이벤트
0.4kg 몸무게 변했음 이벤트
-1.2kg 몸무게 변했음 이벤트
...
-0.1kg 몸무게 변했음 이벤트
-0.5kg 몸무게 변했음 이벤트
1.2kg 몸무게 변했음 이벤트
-0.8kg 몸무게 변했음 이벤트
0.2kg 몸무게 변했음 이벤트
발생 순서대로
최초 이벤트부터
마지막 이벤트까지
차례대로 재현하면
현재(최종) 상태가
됨!
이벤트 저장소(Event Store)
10	
  
이벤트 소싱과 도메인 구현
도메인의 기능
•  암호 변경 예: 엔티티 로딩+로직+상태 변경
– 엔티티(객체 또는 데이터) 로딩
•  로직 수행에 필요한 엔티티 객체(또는 데이터) 로딩
– 로직
•  oldPw가 현재 암호와 일치하는지 검사
•  일치하지 않으면, 암호 변경 실패(예, 익셉션 발생)
– 상태 변경
•  암호를 newPw로 변경함
11
도메인 구현 : 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
상태
변경	
  
도메인 구현
이벤트 소싱 전 - 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	
  
영역 적용 전 (RDBMS/ORM) 적용 후
도메인 객체 로딩 SQL : SELECT 쿼리
ORM : 프레임워크가 매핑 설정
을 이용해서 SELECT 쿼리 실행
-  이벤트로부터 도메인 객체 생성
-  도메인 객체의 이벤트 핸들러를
이용해 상태 변경 반영
도메인 기능 SQL : 서비스 클래스
ORM : (일부) 엔티티 클래스
-  도메인 객체가 수행
-  상태 변경을 위한 이벤트 생성
상태 변경 반영
(데이터 변경)
SQL : Insert/Update/Delete 쿼리
ORM : 엔티티 프로퍼티를 변경
하면 ORM 프레임워크가 알맞
은 쿼리 실행
-  도메인 객체가 발생한 이벤트를
저장소에 보관
도메인 객체 로딩 1
•  이벤트 목록 발생 순서대로 저장되어 있다면
15	
  
회원 ID "mad"와 관련된 이벤트
MemberCreatedEvent("mad", "bk", "a@a.com", "pw")
EmailVerifiedEvent("mad")
MemberEnabledEvent("mad")
PasswordChangedEvent("mad", "newPw")
MemberDisabledEvent("mad", "도용의심")
도메인 객체 로딩 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;
}
도메인 객체 로딩 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; }
이
벤
트
	
  
순
차
	
  
적
용
최신	
  
상태	
  
도메인 객체 로딩 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);
}
최신상태	
  
도메인 객체 기능의 변화 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();
}
상태는 이벤트 핸들러에서만 변경	
  
도메인 기능 메서드는 상태를
변경하는 대신, 변경할 정보를
담은 이벤트 생성	
  
상태 변경 이벤트 생성 및 보관	
  상태를 변경시키는 기능	
  
도메인 객체 기능의 변화 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	
  
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. 도메인 객체의 상태 변경
이벤트 보관
•  물리적인 저장소에 보관 필요
– 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	
  
성능
도메인 이벤트가 쌓이면?
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개의 이벤트를 로딩한다면,
전반적인 응답 속도 저하
스냅샷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
스냅샷을 기준으로
이후 버전만 로딩
여러 도메인 객체에 대한 조회는?
•  다음을 이벤트 소싱으로 처리하려면?
– 100만 회원 대상, 최근 1주일 가입 신청자 중
아직 이메일 인증을 하지 않은 회원 찾기
26	
  
1번 객체
로딩Ÿ검사
2번 객체
로딩Ÿ검사
100만번 객체
로딩Ÿ검사
3번 객체
로딩Ÿ검사
................	
  
겁나게 느린 조회 응답 속도!
조회 전용 모델로 조회 속도 향상
•  시스템을 다음의 두 가지로 분리
– 기능 실행(Command) / 상태 조회(Query)
•  CQRS(Command Query Responsibility Segregation)
27	
  
이벤트 소싱 기반
도메인 모델
데이터 기반
조회 모델 구현
이벤트 스토어 데이터 저장소
UI	
  
상태를 변경하는 명령은
이벤트 소싱 기반 모델에
전달
상태 조회 요청은
데이터 기반 모델에
전달
이벤트를
전달해서
조회에 맞는
모델 생성
뷰 전용 모델	
  
조회 전용 모델 처리 예
28	
  
MemberCreated
Event
EmailVerified
Event
insert into NOVERIEDMEM
values (....)
Event
Store
delete from NOVERIEDMEM
where ...
RDBMS
UI	
   데이터 조회	
  
29	
  
기능 변경의 유연함
주문 도메인 취소 예
•  이벤트 소싱 기반 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;
}
}
취소 시간을 추가하려면?
•  이벤트 소싱 기반 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(); // 수정전발생도메인객체에도기능적용
}
}
추가한 코드
만약 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 = ?
주문 금액을 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);
}
…
}
새로운 이벤트 타입으로	
  
기존 이벤트 데이터 영향없이
구현 변경
기존 이벤트에 대한 	
  
어렵지 않은 호환성 처리
만약 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	
  
정리
장점
•  DB에 의존적이지 않은 도메인 코드 구현
–  테이블이나 ORM 기술의 제한/제약에서 벗어남
•  기능 변경
–  하위 호환 처리가 상대적으로 쉬움
–  이벤트로부터 완전히 새로운 도메인 객체의 생성도 가능
•  버그 추적 용이
–  이벤트를 차례대로 검사하면서 버그 원인 추적 가능
•  객체 지향/DDD와 좋은 궁합
–  복잡한 도메인을 객체 지향적으로 구현하기에 좋음
•  CQRS와 좋은 궁합
–  조회 관련 코드를 도메인에서 분리
–  조회 모델 분리로 조회 성능 향상 가능
36	
  
단점
•  익숙하지 않음
–  SQL 위주(데이터 중심) 개발 성향인 경우 적응 힘듬
•  단순 모델에는 적합하지 않음
–  단순 모델에 적용하기엔 구현이 복잡해짐
•  도구 부족
–  이벤트 소싱과 CQRS 지원 프레임워크 부족
•  운영시 어려움
–  이벤트 데이터만으로는 최신 상태의 빠른 확인 불가
•  CQRS 필수!
37	
  
참고자료
•  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	
  
끝

More Related Content

What's hot

Massive service basic
Massive service basicMassive service basic
Massive service basicDaeMyung Kang
 
ksqlDB로 시작하는 스트림 프로세싱
ksqlDB로 시작하는 스트림 프로세싱ksqlDB로 시작하는 스트림 프로세싱
ksqlDB로 시작하는 스트림 프로세싱confluent
 
[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020Ji-Woong Choi
 
webservice scaling for newbie
webservice scaling for newbiewebservice scaling for newbie
webservice scaling for newbieDaeMyung Kang
 
객체지향적인 도메인 레이어 구축하기
객체지향적인 도메인 레이어 구축하기객체지향적인 도메인 레이어 구축하기
객체지향적인 도메인 레이어 구축하기Young-Ho Cho
 
[수정본] 우아한 객체지향
[수정본] 우아한 객체지향[수정본] 우아한 객체지향
[수정본] 우아한 객체지향Young-Ho Cho
 
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유Hyojun Jeon
 
Elastic Stack 을 이용한 게임 서비스 통합 로깅 플랫폼 - elastic{on} 2019 Seoul
Elastic Stack 을 이용한 게임 서비스 통합 로깅 플랫폼 - elastic{on} 2019 SeoulElastic Stack 을 이용한 게임 서비스 통합 로깅 플랫폼 - elastic{on} 2019 Seoul
Elastic Stack 을 이용한 게임 서비스 통합 로깅 플랫폼 - elastic{on} 2019 SeoulSeungYong Oh
 
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기SeungYong Oh
 
[2D1]Elasticsearch 성능 최적화
[2D1]Elasticsearch 성능 최적화[2D1]Elasticsearch 성능 최적화
[2D1]Elasticsearch 성능 최적화NAVER D2
 
Elastic Search (엘라스틱서치) 입문
Elastic Search (엘라스틱서치) 입문Elastic Search (엘라스틱서치) 입문
Elastic Search (엘라스틱서치) 입문SeungHyun Eom
 
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유 (2부)
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유 (2부)[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유 (2부)
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유 (2부)Hyojun Jeon
 
SpringBoot with MyBatis, Flyway, QueryDSL
SpringBoot with MyBatis, Flyway, QueryDSLSpringBoot with MyBatis, Flyway, QueryDSL
SpringBoot with MyBatis, Flyway, QueryDSLSunghyouk Bae
 
Federated Engine 실무적용사례
Federated Engine 실무적용사례Federated Engine 실무적용사례
Federated Engine 실무적용사례I Goo Lee
 
김민욱, (달빛조각사) 엘릭서를 이용한 mmorpg 서버 개발, NDC2019
김민욱, (달빛조각사) 엘릭서를 이용한 mmorpg 서버 개발, NDC2019김민욱, (달빛조각사) 엘릭서를 이용한 mmorpg 서버 개발, NDC2019
김민욱, (달빛조각사) 엘릭서를 이용한 mmorpg 서버 개발, NDC2019min woog kim
 
MSA 전략 1: 마이크로서비스, 어떻게 디자인 할 것인가?
MSA 전략 1: 마이크로서비스, 어떻게 디자인 할 것인가?MSA 전략 1: 마이크로서비스, 어떻게 디자인 할 것인가?
MSA 전략 1: 마이크로서비스, 어떻게 디자인 할 것인가?VMware Tanzu Korea
 
쿠키런 1년, 서버개발 분투기
쿠키런 1년, 서버개발 분투기쿠키런 1년, 서버개발 분투기
쿠키런 1년, 서버개발 분투기Brian Hong
 
로그 기깔나게 잘 디자인하는 법
로그 기깔나게 잘 디자인하는 법로그 기깔나게 잘 디자인하는 법
로그 기깔나게 잘 디자인하는 법Jeongsang Baek
 
[135] 오픈소스 데이터베이스, 은행 서비스에 첫발을 내밀다.
[135] 오픈소스 데이터베이스, 은행 서비스에 첫발을 내밀다.[135] 오픈소스 데이터베이스, 은행 서비스에 첫발을 내밀다.
[135] 오픈소스 데이터베이스, 은행 서비스에 첫발을 내밀다.NAVER D2
 

What's hot (20)

Massive service basic
Massive service basicMassive service basic
Massive service basic
 
ksqlDB로 시작하는 스트림 프로세싱
ksqlDB로 시작하는 스트림 프로세싱ksqlDB로 시작하는 스트림 프로세싱
ksqlDB로 시작하는 스트림 프로세싱
 
[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020
 
webservice scaling for newbie
webservice scaling for newbiewebservice scaling for newbie
webservice scaling for newbie
 
객체지향적인 도메인 레이어 구축하기
객체지향적인 도메인 레이어 구축하기객체지향적인 도메인 레이어 구축하기
객체지향적인 도메인 레이어 구축하기
 
[수정본] 우아한 객체지향
[수정본] 우아한 객체지향[수정본] 우아한 객체지향
[수정본] 우아한 객체지향
 
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유
 
Elastic Stack 을 이용한 게임 서비스 통합 로깅 플랫폼 - elastic{on} 2019 Seoul
Elastic Stack 을 이용한 게임 서비스 통합 로깅 플랫폼 - elastic{on} 2019 SeoulElastic Stack 을 이용한 게임 서비스 통합 로깅 플랫폼 - elastic{on} 2019 Seoul
Elastic Stack 을 이용한 게임 서비스 통합 로깅 플랫폼 - elastic{on} 2019 Seoul
 
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
 
[2D1]Elasticsearch 성능 최적화
[2D1]Elasticsearch 성능 최적화[2D1]Elasticsearch 성능 최적화
[2D1]Elasticsearch 성능 최적화
 
Elastic Search (엘라스틱서치) 입문
Elastic Search (엘라스틱서치) 입문Elastic Search (엘라스틱서치) 입문
Elastic Search (엘라스틱서치) 입문
 
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유 (2부)
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유 (2부)[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유 (2부)
[NDC18] 야생의 땅 듀랑고의 데이터 엔지니어링 이야기: 로그 시스템 구축 경험 공유 (2부)
 
SpringBoot with MyBatis, Flyway, QueryDSL
SpringBoot with MyBatis, Flyway, QueryDSLSpringBoot with MyBatis, Flyway, QueryDSL
SpringBoot with MyBatis, Flyway, QueryDSL
 
Federated Engine 실무적용사례
Federated Engine 실무적용사례Federated Engine 실무적용사례
Federated Engine 실무적용사례
 
Rest api-basic
Rest api-basicRest api-basic
Rest api-basic
 
김민욱, (달빛조각사) 엘릭서를 이용한 mmorpg 서버 개발, NDC2019
김민욱, (달빛조각사) 엘릭서를 이용한 mmorpg 서버 개발, NDC2019김민욱, (달빛조각사) 엘릭서를 이용한 mmorpg 서버 개발, NDC2019
김민욱, (달빛조각사) 엘릭서를 이용한 mmorpg 서버 개발, NDC2019
 
MSA 전략 1: 마이크로서비스, 어떻게 디자인 할 것인가?
MSA 전략 1: 마이크로서비스, 어떻게 디자인 할 것인가?MSA 전략 1: 마이크로서비스, 어떻게 디자인 할 것인가?
MSA 전략 1: 마이크로서비스, 어떻게 디자인 할 것인가?
 
쿠키런 1년, 서버개발 분투기
쿠키런 1년, 서버개발 분투기쿠키런 1년, 서버개발 분투기
쿠키런 1년, 서버개발 분투기
 
로그 기깔나게 잘 디자인하는 법
로그 기깔나게 잘 디자인하는 법로그 기깔나게 잘 디자인하는 법
로그 기깔나게 잘 디자인하는 법
 
[135] 오픈소스 데이터베이스, 은행 서비스에 첫발을 내밀다.
[135] 오픈소스 데이터베이스, 은행 서비스에 첫발을 내밀다.[135] 오픈소스 데이터베이스, 은행 서비스에 첫발을 내밀다.
[135] 오픈소스 데이터베이스, 은행 서비스에 첫발을 내밀다.
 

Similar to Event source 학습 내용 공유

Mongo db 시작하기
Mongo db 시작하기Mongo db 시작하기
Mongo db 시작하기OnGameServer
 
Node.js and react
Node.js and reactNode.js and react
Node.js and reactHyungKuIm
 
모바일 게임과 앱을 위한 오픈소스 게임서버 엔진 프로젝트 CloudBread 프로젝트
모바일 게임과 앱을 위한 오픈소스 게임서버 엔진 프로젝트 CloudBread 프로젝트모바일 게임과 앱을 위한 오픈소스 게임서버 엔진 프로젝트 CloudBread 프로젝트
모바일 게임과 앱을 위한 오픈소스 게임서버 엔진 프로젝트 CloudBread 프로젝트Dae Kim
 
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용중선 곽
 
From event storming to spring cloud implementation
From event storming to spring cloud implementationFrom event storming to spring cloud implementation
From event storming to spring cloud implementationuEngine Solutions
 
Nodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjsNodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjs기동 이
 
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptxYeongKiKim1
 
Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)beom kyun choi
 
20140528 AWS Meister BlackBelt - Amazon Kinesis (Korean)
20140528 AWS Meister BlackBelt - Amazon Kinesis (Korean)20140528 AWS Meister BlackBelt - Amazon Kinesis (Korean)
20140528 AWS Meister BlackBelt - Amazon Kinesis (Korean)Amazon Web Services Korea
 
2회 오픈소스 게임 서버 엔진 스터디 캠프 - CloudBread
2회 오픈소스 게임 서버 엔진 스터디 캠프 - CloudBread2회 오픈소스 게임 서버 엔진 스터디 캠프 - CloudBread
2회 오픈소스 게임 서버 엔진 스터디 캠프 - CloudBreadDae Kim
 
[243]kaleido 노현걸
[243]kaleido 노현걸[243]kaleido 노현걸
[243]kaleido 노현걸NAVER D2
 
Facebook은 React를 왜 만들었을까?
Facebook은 React를 왜 만들었을까? Facebook은 React를 왜 만들었을까?
Facebook은 React를 왜 만들었을까? Kim Hunmin
 
모바일 게임 하이브 런칭기 - 최용호
모바일 게임 하이브 런칭기 - 최용호모바일 게임 하이브 런칭기 - 최용호
모바일 게임 하이브 런칭기 - 최용호용호 최
 
자바스크립트 프레임워크 살펴보기
자바스크립트 프레임워크 살펴보기자바스크립트 프레임워크 살펴보기
자바스크립트 프레임워크 살펴보기Jeado Ko
 
[AWSKRUG] 모바일게임 하이브 런칭기 (2018)
[AWSKRUG] 모바일게임 하이브 런칭기 (2018)[AWSKRUG] 모바일게임 하이브 런칭기 (2018)
[AWSKRUG] 모바일게임 하이브 런칭기 (2018)용호 최
 
Web Analytics at Scale with Elasticsearch @ naver.com - Part 2 - Lessons Learned
Web Analytics at Scale with Elasticsearch @ naver.com - Part 2 - Lessons LearnedWeb Analytics at Scale with Elasticsearch @ naver.com - Part 2 - Lessons Learned
Web Analytics at Scale with Elasticsearch @ naver.com - Part 2 - Lessons LearnedJungsu Heo
 
Python codelab2
Python codelab2Python codelab2
Python codelab2건희 김
 
CloudWatch 성능 모니터링과 신속한 대응을 위한 노하우 - 박선용 솔루션즈 아키텍트:: AWS Cloud Track 3 Gaming
CloudWatch 성능 모니터링과 신속한 대응을 위한 노하우 - 박선용 솔루션즈 아키텍트:: AWS Cloud Track 3 GamingCloudWatch 성능 모니터링과 신속한 대응을 위한 노하우 - 박선용 솔루션즈 아키텍트:: AWS Cloud Track 3 Gaming
CloudWatch 성능 모니터링과 신속한 대응을 위한 노하우 - 박선용 솔루션즈 아키텍트:: AWS Cloud Track 3 GamingAmazon Web Services Korea
 
02.실행환경 실습교재(데이터처리)
02.실행환경 실습교재(데이터처리)02.실행환경 실습교재(데이터처리)
02.실행환경 실습교재(데이터처리)Hankyo
 

Similar to Event source 학습 내용 공유 (20)

Mongo db 시작하기
Mongo db 시작하기Mongo db 시작하기
Mongo db 시작하기
 
Node.js and react
Node.js and reactNode.js and react
Node.js and react
 
모바일 게임과 앱을 위한 오픈소스 게임서버 엔진 프로젝트 CloudBread 프로젝트
모바일 게임과 앱을 위한 오픈소스 게임서버 엔진 프로젝트 CloudBread 프로젝트모바일 게임과 앱을 위한 오픈소스 게임서버 엔진 프로젝트 CloudBread 프로젝트
모바일 게임과 앱을 위한 오픈소스 게임서버 엔진 프로젝트 CloudBread 프로젝트
 
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
 
From event storming to spring cloud implementation
From event storming to spring cloud implementationFrom event storming to spring cloud implementation
From event storming to spring cloud implementation
 
Nodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjsNodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjs
 
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx
광안 1반 2팀 엠퀴즈 최종 발표 자료.pptx
 
Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)
 
20140528 AWS Meister BlackBelt - Amazon Kinesis (Korean)
20140528 AWS Meister BlackBelt - Amazon Kinesis (Korean)20140528 AWS Meister BlackBelt - Amazon Kinesis (Korean)
20140528 AWS Meister BlackBelt - Amazon Kinesis (Korean)
 
2회 오픈소스 게임 서버 엔진 스터디 캠프 - CloudBread
2회 오픈소스 게임 서버 엔진 스터디 캠프 - CloudBread2회 오픈소스 게임 서버 엔진 스터디 캠프 - CloudBread
2회 오픈소스 게임 서버 엔진 스터디 캠프 - CloudBread
 
[243]kaleido 노현걸
[243]kaleido 노현걸[243]kaleido 노현걸
[243]kaleido 노현걸
 
테스트
테스트테스트
테스트
 
Facebook은 React를 왜 만들었을까?
Facebook은 React를 왜 만들었을까? Facebook은 React를 왜 만들었을까?
Facebook은 React를 왜 만들었을까?
 
모바일 게임 하이브 런칭기 - 최용호
모바일 게임 하이브 런칭기 - 최용호모바일 게임 하이브 런칭기 - 최용호
모바일 게임 하이브 런칭기 - 최용호
 
자바스크립트 프레임워크 살펴보기
자바스크립트 프레임워크 살펴보기자바스크립트 프레임워크 살펴보기
자바스크립트 프레임워크 살펴보기
 
[AWSKRUG] 모바일게임 하이브 런칭기 (2018)
[AWSKRUG] 모바일게임 하이브 런칭기 (2018)[AWSKRUG] 모바일게임 하이브 런칭기 (2018)
[AWSKRUG] 모바일게임 하이브 런칭기 (2018)
 
Web Analytics at Scale with Elasticsearch @ naver.com - Part 2 - Lessons Learned
Web Analytics at Scale with Elasticsearch @ naver.com - Part 2 - Lessons LearnedWeb Analytics at Scale with Elasticsearch @ naver.com - Part 2 - Lessons Learned
Web Analytics at Scale with Elasticsearch @ naver.com - Part 2 - Lessons Learned
 
Python codelab2
Python codelab2Python codelab2
Python codelab2
 
CloudWatch 성능 모니터링과 신속한 대응을 위한 노하우 - 박선용 솔루션즈 아키텍트:: AWS Cloud Track 3 Gaming
CloudWatch 성능 모니터링과 신속한 대응을 위한 노하우 - 박선용 솔루션즈 아키텍트:: AWS Cloud Track 3 GamingCloudWatch 성능 모니터링과 신속한 대응을 위한 노하우 - 박선용 솔루션즈 아키텍트:: AWS Cloud Track 3 Gaming
CloudWatch 성능 모니터링과 신속한 대응을 위한 노하우 - 박선용 솔루션즈 아키텍트:: AWS Cloud Track 3 Gaming
 
02.실행환경 실습교재(데이터처리)
02.실행환경 실습교재(데이터처리)02.실행환경 실습교재(데이터처리)
02.실행환경 실습교재(데이터처리)
 

More from beom kyun choi

옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개beom kyun choi
 
DDD로 복잡함 다루기
DDD로 복잡함 다루기DDD로 복잡함 다루기
DDD로 복잡함 다루기beom kyun choi
 
TDD 발담그기 @ 공감세미나
TDD 발담그기 @ 공감세미나TDD 발담그기 @ 공감세미나
TDD 발담그기 @ 공감세미나beom kyun choi
 
keras 빨리 훑어보기(intro)
keras 빨리 훑어보기(intro)keras 빨리 훑어보기(intro)
keras 빨리 훑어보기(intro)beom kyun choi
 
Tensorflow regression 텐서플로우 회귀
Tensorflow regression 텐서플로우 회귀Tensorflow regression 텐서플로우 회귀
Tensorflow regression 텐서플로우 회귀beom kyun choi
 
Ddd start 부록 지앤선&ksug
Ddd start 부록 지앤선&ksugDdd start 부록 지앤선&ksug
Ddd start 부록 지앤선&ksugbeom kyun choi
 
파이썬 언어 기초
파이썬 언어 기초파이썬 언어 기초
파이썬 언어 기초beom kyun choi
 
도메인구현 KSUG 20151128
도메인구현 KSUG 20151128도메인구현 KSUG 20151128
도메인구현 KSUG 20151128beom kyun choi
 
모델링 연습 리뷰
모델링 연습 리뷰모델링 연습 리뷰
모델링 연습 리뷰beom kyun choi
 
ALS WS에 대한 이해 자료
ALS WS에 대한 이해 자료ALS WS에 대한 이해 자료
ALS WS에 대한 이해 자료beom kyun choi
 
리뷰의 기술 소개
리뷰의 기술 소개리뷰의 기술 소개
리뷰의 기술 소개beom kyun choi
 
스프링 시큐리티 구조 이해
스프링 시큐리티 구조 이해스프링 시큐리티 구조 이해
스프링 시큐리티 구조 이해beom kyun choi
 
자바8 스트림 API 소개
자바8 스트림 API 소개자바8 스트림 API 소개
자바8 스트림 API 소개beom kyun choi
 
자바8 람다식 소개
자바8 람다식 소개자바8 람다식 소개
자바8 람다식 소개beom kyun choi
 
하둡2 YARN 짧게 보기
하둡2 YARN 짧게 보기하둡2 YARN 짧게 보기
하둡2 YARN 짧게 보기beom kyun choi
 
차원축소 훑어보기 (PCA, SVD, NMF)
차원축소 훑어보기 (PCA, SVD, NMF)차원축소 훑어보기 (PCA, SVD, NMF)
차원축소 훑어보기 (PCA, SVD, NMF)beom kyun choi
 

More from beom kyun choi (20)

옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
 
DDD로 복잡함 다루기
DDD로 복잡함 다루기DDD로 복잡함 다루기
DDD로 복잡함 다루기
 
TDD 발담그기 @ 공감세미나
TDD 발담그기 @ 공감세미나TDD 발담그기 @ 공감세미나
TDD 발담그기 @ 공감세미나
 
keras 빨리 훑어보기(intro)
keras 빨리 훑어보기(intro)keras 빨리 훑어보기(intro)
keras 빨리 훑어보기(intro)
 
DDD 준비 서문래
DDD 준비 서문래DDD 준비 서문래
DDD 준비 서문래
 
Tensorflow regression 텐서플로우 회귀
Tensorflow regression 텐서플로우 회귀Tensorflow regression 텐서플로우 회귀
Tensorflow regression 텐서플로우 회귀
 
Ddd start 부록 지앤선&ksug
Ddd start 부록 지앤선&ksugDdd start 부록 지앤선&ksug
Ddd start 부록 지앤선&ksug
 
MVP 패턴 소개
MVP 패턴 소개MVP 패턴 소개
MVP 패턴 소개
 
파이썬 언어 기초
파이썬 언어 기초파이썬 언어 기초
파이썬 언어 기초
 
도메인구현 KSUG 20151128
도메인구현 KSUG 20151128도메인구현 KSUG 20151128
도메인구현 KSUG 20151128
 
Spring Boot 소개
Spring Boot 소개Spring Boot 소개
Spring Boot 소개
 
모델링 연습 리뷰
모델링 연습 리뷰모델링 연습 리뷰
모델링 연습 리뷰
 
ALS WS에 대한 이해 자료
ALS WS에 대한 이해 자료ALS WS에 대한 이해 자료
ALS WS에 대한 이해 자료
 
리뷰의 기술 소개
리뷰의 기술 소개리뷰의 기술 소개
리뷰의 기술 소개
 
스프링 시큐리티 구조 이해
스프링 시큐리티 구조 이해스프링 시큐리티 구조 이해
스프링 시큐리티 구조 이해
 
자바8 스트림 API 소개
자바8 스트림 API 소개자바8 스트림 API 소개
자바8 스트림 API 소개
 
자바8 람다식 소개
자바8 람다식 소개자바8 람다식 소개
자바8 람다식 소개
 
Zookeeper 소개
Zookeeper 소개Zookeeper 소개
Zookeeper 소개
 
하둡2 YARN 짧게 보기
하둡2 YARN 짧게 보기하둡2 YARN 짧게 보기
하둡2 YARN 짧게 보기
 
차원축소 훑어보기 (PCA, SVD, NMF)
차원축소 훑어보기 (PCA, SVD, NMF)차원축소 훑어보기 (PCA, SVD, NMF)
차원축소 훑어보기 (PCA, SVD, NMF)
 

Event source 학습 내용 공유

  • 1. 이벤트 소싱(Event Sourcing) 학습 내용 공유 신림프로그래머, 최범균, 2015-03-21 madvirus@madvirus.net
  • 2. 다룰 내용 •  이벤트와 이벤트 소싱 •  이벤트 소싱을 이용한 도메인 구현 •  성능 •  정리 2  
  • 3. 지난 70일간의 몸무게 증감 기록 •  전날과의 무게 차이 기록 -­‐2.5   -­‐2   -­‐1.5   -­‐1   -­‐0.5   0   0.5   1   1.5   3  
  • 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   당일공개 (-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. -­‐3   -­‐2   -­‐1   0   1   2   이벤트 •  과거에 벌어진 어떤 것을 이벤트로 정의 – 주로 상태의 변화 6   몸무게가 1Kg 줄었음 몸무게가 1Kg 늘었음
  • 7. 이벤트의 구성 •  구성 – 생성자, 일렬번호/버전, 타입, 발생 시간 – 내용(payload) •  예, 회원 암호 변경 이벤트 – 생성자: "회원mad" – 버전: "1" – 타입: "PasswordChangedEvent" – 발생시간: 2015-03-21 09:59:59 – 내용: {id:"mad", newPwd: "xxxx"} 7
  • 8. 이벤트 소싱(Event Sourcing) 8   * http://martinfowler.com/eaaDev/EventSourcing.html 어플리케이션의모든상태변화를 순서에따라이벤트로보관한다. Captureallchangestoanapplicationstate asasequenceofevents.
  • 9. 이벤트 소싱과 현재 상태 9   -0.3kg 몸무게 변했음 이벤트 -0.6kg 몸무게 변했음 이벤트 0.4kg 몸무게 변했음 이벤트 -1.2kg 몸무게 변했음 이벤트 ... -0.1kg 몸무게 변했음 이벤트 -0.5kg 몸무게 변했음 이벤트 1.2kg 몸무게 변했음 이벤트 -0.8kg 몸무게 변했음 이벤트 0.2kg 몸무게 변했음 이벤트 발생 순서대로 최초 이벤트부터 마지막 이벤트까지 차례대로 재현하면 현재(최종) 상태가 됨! 이벤트 저장소(Event Store)
  • 10. 10   이벤트 소싱과 도메인 구현
  • 11. 도메인의 기능 •  암호 변경 예: 엔티티 로딩+로직+상태 변경 – 엔티티(객체 또는 데이터) 로딩 •  로직 수행에 필요한 엔티티 객체(또는 데이터) 로딩 – 로직 •  oldPw가 현재 암호와 일치하는지 검사 •  일치하지 않으면, 암호 변경 실패(예, 익셉션 발생) – 상태 변경 •  암호를 newPw로 변경함 11
  • 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. 도메인 구현 이벤트 소싱 전 - 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   영역 적용 전 (RDBMS/ORM) 적용 후 도메인 객체 로딩 SQL : SELECT 쿼리 ORM : 프레임워크가 매핑 설정 을 이용해서 SELECT 쿼리 실행 -  이벤트로부터 도메인 객체 생성 -  도메인 객체의 이벤트 핸들러를 이용해 상태 변경 반영 도메인 기능 SQL : 서비스 클래스 ORM : (일부) 엔티티 클래스 -  도메인 객체가 수행 -  상태 변경을 위한 이벤트 생성 상태 변경 반영 (데이터 변경) SQL : Insert/Update/Delete 쿼리 ORM : 엔티티 프로퍼티를 변경 하면 ORM 프레임워크가 알맞 은 쿼리 실행 -  도메인 객체가 발생한 이벤트를 저장소에 보관
  • 15. 도메인 객체 로딩 1 •  이벤트 목록 발생 순서대로 저장되어 있다면 15   회원 ID "mad"와 관련된 이벤트 MemberCreatedEvent("mad", "bk", "a@a.com", "pw") EmailVerifiedEvent("mad") MemberEnabledEvent("mad") PasswordChangedEvent("mad", "newPw") MemberDisabledEvent("mad", "도용의심")
  • 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. 도메인 객체 로딩 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. 도메인 객체 로딩 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. 도메인 객체 기능의 변화 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. 상태 변경 이벤트 생성 및 보관  상태를 변경시키는 기능   도메인 객체 기능의 변화 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   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. 이벤트 보관 •  물리적인 저장소에 보관 필요 – 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"}
  • 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. 스냅샷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. 여러 도메인 객체에 대한 조회는? •  다음을 이벤트 소싱으로 처리하려면? – 100만 회원 대상, 최근 1주일 가입 신청자 중 아직 이메일 인증을 하지 않은 회원 찾기 26   1번 객체 로딩Ÿ검사 2번 객체 로딩Ÿ검사 100만번 객체 로딩Ÿ검사 3번 객체 로딩Ÿ검사 ................   겁나게 느린 조회 응답 속도!
  • 27. 조회 전용 모델로 조회 속도 향상 •  시스템을 다음의 두 가지로 분리 – 기능 실행(Command) / 상태 조회(Query) •  CQRS(Command Query Responsibility Segregation) 27   이벤트 소싱 기반 도메인 모델 데이터 기반 조회 모델 구현 이벤트 스토어 데이터 저장소 UI   상태를 변경하는 명령은 이벤트 소싱 기반 모델에 전달 상태 조회 요청은 데이터 기반 모델에 전달 이벤트를 전달해서 조회에 맞는 모델 생성
  • 28. 뷰 전용 모델   조회 전용 모델 처리 예 28   MemberCreated Event EmailVerified Event insert into NOVERIEDMEM values (....) Event Store delete from NOVERIEDMEM where ... RDBMS UI   데이터 조회  
  • 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. 취소 시간을 추가하려면? •  이벤트 소싱 기반 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. 만약 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. 주문 금액을 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. 만약 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()); …
  • 36. 장점 •  DB에 의존적이지 않은 도메인 코드 구현 –  테이블이나 ORM 기술의 제한/제약에서 벗어남 •  기능 변경 –  하위 호환 처리가 상대적으로 쉬움 –  이벤트로부터 완전히 새로운 도메인 객체의 생성도 가능 •  버그 추적 용이 –  이벤트를 차례대로 검사하면서 버그 원인 추적 가능 •  객체 지향/DDD와 좋은 궁합 –  복잡한 도메인을 객체 지향적으로 구현하기에 좋음 •  CQRS와 좋은 궁합 –  조회 관련 코드를 도메인에서 분리 –  조회 모델 분리로 조회 성능 향상 가능 36  
  • 37. 단점 •  익숙하지 않음 –  SQL 위주(데이터 중심) 개발 성향인 경우 적응 힘듬 •  단순 모델에는 적합하지 않음 –  단순 모델에 적용하기엔 구현이 복잡해짐 •  도구 부족 –  이벤트 소싱과 CQRS 지원 프레임워크 부족 •  운영시 어려움 –  이벤트 데이터만으로는 최신 상태의 빠른 확인 불가 •  CQRS 필수! 37  
  • 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