728x90
반응형
728x90
반응형
반응형

들어가며..

마이크로 서비스 아키텍처(MSA) 프로젝트를 개발 및 운영을 하다 보면 도메인 모델은 복잡해지고 점점 설계 시점의 의도와는 다른 방향으로 변질되는 일이 빈번히 발생한다. 특히 요즘처럼 고차원적인 UX, 급변하는 IT 시장의 흐름으로 인해 시도 때도 없이 달라지는 기획팀/사업부의 요구사항을 충족하는 모델을 만드는 건 더욱 어려운 일이 되었다. 게다가 이렇게 복잡한 내용을 하나의 화면에서 다 보여달라고 하니.. 아무리 인덱스를 추가하고 쿼리를 튜닝하더라도 조회 속도가 나지 않고, n번의 api를 결과를 합치면서 생기는 실수, 더러운 소스코드 등은 결국 서비스의 질을 낮추기에 충분해진다.

(내가 경험한) MSA

이게 왜 어려워졌을까? 데이터의 변경과 조회 시 필요한 데이터가 관점에 따라 명백히 다른데, 이걸 하나의 모델/애플리케이션/디비에서 해결하려다 보니 (각 영역에서 필요하지 않은 속성들로 인해) 복잡도가 증가하고 변질되는 것은 아닐까?

그럼 어떻게 이 문제를 해결 할 수 있을까? 데이터의 변경과 조회를 나누면 되지 않을까? 해서 나온 게 CQRS이다.

 

CQRS란?

마이크로 서비스의 패턴이 무려 44가지나 있다고 하는데, 간단하게 관련 용어 몇 가지만 살펴본다.

  •  DDD - Domain Driven Design 도메인 주도 개발(방법론)
    • 비즈니스를 도메인 별로 나누어 설계하는 방식/접근법
  • EDA - Event Driven Architecture
    • 분산된 시스템 간에 이벤트를 생성, 발행(publishing)하고 발행된 이벤트를 필요로 하는 수신자(subscriber)에게 전송되는 흐름으로 구성된 아키텍처로 이벤트를 수신한 수신자가 이벤트를 처리하는 형태임
    • Architectural Patterns
  • CQRS - Command Query Responsibility Segregation 패턴 
    • "CQRS는 DDD 기반의 Object Model 방법론 적용 시 나타났던 문제점들을 해결하기 위해 등장했다"
  • Aggregate Pattern
  • Saga Pattern
    • MSA 기반의 분산 시스템에서 분산된 DB의 정합성을 보장하는 방법, 트랜젝션 관리주체는 애플리케이션(not DB)
    • sequence diagram을 따라가다(transaction flow 속에서) rollback이 필요할 때 처리하는 방법
    • Choreography-Based Saga(각자 알아서 처리)
    • Orchestration-Based Saga(중앙 컨트롤 타워가 정리)
DDD is well suited with some architectural patterns like CQRS, Event Driven Architecture, Event sourcing, etc but they are not required to use DDD.

 

CQRS를 구현할 수 있게 하는 axon framework

  • axon framework는 DDD 패러다임 하에서 event sourcing과 CQRS 패턴을 이용해 애플리케이션을 작성할 수 있도록 도와주는 framework
  • 작성 글: [cqrs] axon framework란

실습

환경: springboot2.6.2 / axon framework 4.2.1 + axon server / postgresql14

1. axon server를 설치하고

2022.01.12 - [서버 세팅 & tool/vm on mac] - [vm] axon server 설치

 

[vm] axon server 설치

2022.01.03 - [세팅/vm on mac] - [vm] virtual box centos7 세팅 [vm] virtual box centos7 세팅 1. oracle virtual box for mac 설치 2. centos7 iso 받기 https://ftp-srv2.kddilabs.jp/Linux/packages/CentOS/..

bangpurin.tistory.com

2. springboot project에 axon dependencies 올려서 개발

axonVersion = "4.2.1"

implementation group: 'org.axonframework', name: 'axon-spring-boot-starter', version: "$axonVersion"
implementation group: 'org.axonframework', name: 'axon-configuration', version: "$axonVersion"

3. 블로그 흐름 따라가며 개발하였고

https://cla9.tistory.com/2?category=814447 

 

1. Axon Framework 개요

1. 개요 앞으로 진행될 포스팅은 MSA에 관심 많은 분을 대상으로 DDD, CQRS 및 Event Sourcing 내용을 알고 있다고 가정하고, Spring 환경에서 AxonFramework를 활용해 개념 구현하는 방법에 대해 소개하고자

cla9.tistory.com

4. 개발하면서 막히는 부분/ 깨달은 부분은 따로 정리

2022.01.12 - [개발/axon framework] - [axon] command/query project 생성 - clone coding

5. 깃에 등록

https://github.com/haileyjhbang/cqrs-clone.git


axon framework 구조

axon framework

Command Applcation

  • Event-Sourced Aggregate: EventStore로부터 Event를 재생하면서 모델을 최신 상태로
  • State-Stored Aggregate: EventStore에 Event를 적재와 별개로 모델 자체에 최신 상태를 DB에 저장

Query Application

  • Point to Point Query: 하나의 QueryHandler를 찾아 진행
  • Scatter-Gather Query: 동일한 Query를 처리하는 Handler가 여러 App에 등록되어있을 때, 이를 처리하는 방법
  • Subscription Query: Point to Point Query를 요청하였을 때, 만약 Query를 수행하는 Read Model이 바뀌었다면, 화면에 출력되는 결과와 Read Model 사이 데이터 정합성 불일치 문제가 발생한다. 따라서 이를 해결하기 위해 주기적으로 Query를 재요청하는 방식

 

추가) Saga pattern in axon

axon framework에서도 분산 트랜젝션을 위해 saga 패턴을 지원한다(saga event 정보를 db에 저장).

//어노테이션
@StartSaga
@SagaEventHandler(associationProperty = "transferID")
@EndSaga


//함수 호출방식
SagaLifecycle.associateWith("accountID", event.getDstAccountID());
SagaLifecycle.end();

[axon - saga; orchestration] https://cla9.tistory.com/22?category=814447

 

활용방안?

초기 그림의 MSA 구조의 시스템 & 대용량 처리가 필요한 시스템에 적용하면 효과적인 대안이 될 수 있을 것이란 생각이 들었다. ES 전파만 확실하면 쿼리 성능도 좋아질 것이고 두 애플리케이션의 결합도도 낮아 독립적인 스캐일 업 또한 가능할 것이다.

하지만 러닝 커브의 압박, 분산된 구조로 인한 트러블 포인트의 증가, 서비스 복잡도 증가 그리고 DB를 많이 쓰기 때문에 유능한 DBA나 넉넉한 DB공간 확보 필요 등의 비용이 많이 들 수 있어 비즈니스 로직이 간단한 서비스면 굳이 도입할 필요는 없을 것 같다.


참고

https://docs.axoniq.io/reference-guide/

 

Introduction - Axon Reference Guide

The standard version, called "Axon Server", is open source and free to download and use. It is provided under an AxonIQ-specific open source license. While this license allows you to run the software freely in any environment, it is less permissive than th

docs.axoniq.io

 

책: 마이크로 서비스 패턴(Chris Richardson 저)

http://www.yes24.com/Product/Goods/86542732

 

마이크로서비스 패턴 - YES24

모놀리식 애플리케이션을 마이크로서비스 아키텍처로 성공적으로 전환하는 방법마이크로서비스 아키텍처 기반의 애플리케이션을 성공적으로 구축하려면 새로운 아키텍처의 개념을 이해하는

www.yes24.com

요약본: https://microservices.io/patterns/index.html

 

Microservices Pattern: A pattern language for microservices

Microservices.io is brought to you by Chris Richardson. Experienced software architect, author of POJOs in Action, the creator of the original CloudFoundry.com, and the author of Microservices patterns. Chris helps clients around the world adopt the micros

microservices.io

 

https://app.mural.co/t/cloudingegration6924/m/cloudingegration6924/1598872302455/cb40356de0e1fcc36618a25f5f5e2ed18761f3ca

 

mSVC Patterns

 

app.mural.co

 

728x90
반응형

'개발 > axon framework' 카테고리의 다른 글

[axon] saga (2) - clone coding  (0) 2022.02.18
[axon] saga (1) - clone coding  (0) 2022.02.18
[axon] query handler(2) - clone coding  (0) 2022.02.14
[axon] query handler(1) - clone coding  (0) 2022.02.11
[axon] event upcasting - clone coding  (0) 2022.02.04
반응형

이전 글: 2022.02.18 - [개발/spring] - [axon] saga (1) - clone coding

 

[axon] saga (1) - clone coding

이전 글: 2022.02.14 - [개발/spring] - [axon] query handler(2) - clone coding 클론 코딩 참고 블로그는 다음과 같다: https://cla9.tistory.com/23?category=814447 19. Saga 패턴을 활용한 트랜잭션 관리 -..

bangpurin.tistory.com

클론 코딩 참고 블로그는 다음과 같다:  https://cla9.tistory.com/24?category=814447 

 

20. Saga 패턴을 활용한 트랜잭션 관리 - 3

1. 서론 이번 포스팅은 AxonFramework 관련 마지막 포스팅입니다. Saga 패턴 보상 트랜잭션 구현을 다루겠습니다. 2. Deadline MSA 환경에서는 App이 여러개로 분산되어있으므로 하나의 App이 느려지거나 장

cla9.tistory.com

 

Saga 트랜잭션에서 실패가 났을 경우 recovery 하는 방법에 대한 글이다. 딱히 신기술이나 신개념이 나오는 것은 아니고 exception을  try/catch 문으로 잡아서 catch문에서 새로운 이벤트를 전파시켜 롤백하는 과정이다. 즉, 로직적으로 해결하는 것이라서 흐름만 잘 따라가면 된다.

해당 글에서는 두 가지 케이스에 대해 다룬다.

  1. 타임아웃으로 인한 잔액 복구 케이스
  2. 타임아웃이 났는데 어차피 잔액이 모자라서 진행이 안 되는 케이스(하지만 진행을 했다가 취소하는 방식으로 진행한다; 이벤트는 삭제할 수 없기 때문에 취소 이벤트를 발생해줘야 한다)

각 흐름에 대해 정리한다. 소스를 보면서 따라가야 하며 로그를 같이 보면 훨씬 빠르게 접근할 수 있다. 

소스는 역시 깃허브에..

 

  • 타임아웃으로 인한 잔액 복구 케이스

중간에 요청 없어짐 표시는 무시해도 된다. 처음에 테스트할 때 잔액 차감 성공 이벤트가 로그에 찍혔는데, 잔액을 복구하는 로직이 없어서 이상하다 싶었는데 다시 확인해보니, 잔액 차감 성공(TransferApprovedCommand) 시 로직이 덜 짜져 있어서(if (!isExecutingCompensation && !isAbortingCompensation)이 부분이 없었다.) 잘못된 코드였고, 수정해서 안 나오는 게 맞다는 것을 확인했다. 즉, 잔액 차감 성공(TransferApprovedCommand) 을 받았지만 취소 중이면 차감을 아애 안 하는 로직인 것임.

괄호의 숫자는 로그의 시간이다. 시간순으로 나열한 것은 아니므로 주의해서 확인해야한다..

특이점은  jeju에서는 잔액이 감소(이체 성공)하고 다시 복구(이체 취소)하는 과정을 거치지만, command 쪽은 아예 잔액 변경이 없다.

--------------------command
commandGateway.sendAndWait(MoneyTransferCommand)
@CommandHandler transferMoney(MoneyTransferCommand)
	AggregateLifecycle.apply(MoneyTransferEvent)
///사가 시작
@StartSaga @SagaEventHandler(associationProperty = "transferID") on(MoneyTransferEvent)
	commandGateway.sendAndWait(JejuBankTransferCommand) (17:01:23.740)
	10초 기다리다 익셉션 (17:01:33.744)
--------------------jeju
@CommandHandler on(JejuBankTransferCommand)
	15초 홀딩하던도중 취소 요청을 받긴하지만 홀딩 후 성공처리 먼저함
	AggregateLifecycle.apply(TransferApprovedEvent) (17:01:38.790)
@EventSourcingHandler on(TransferApprovedEvent)
	잔액 차감 (17:01:38.791)
--------------------command
//요청 없어짐 
//{익셉션 (17:01:33.744)시 isExecutingCompensation = true로 바뀌었고 그 후에 들어온 이체 성공(잔액 차감) 요청이므로 진행 안 됨}
//@SagaEventHandler(associationProperty = "srcAccountID") on(TransferApprovedEvent)
//	제주 잔액 차감 성공 이벤트 리슨 후 반영
//	commandGateway.send(TransferApprovedCommand)
//@CommandHandler transferMoney(TransferApprovedCommand)
//	잔액 증가
//	AggregateLifecycle.apply(DepositMoneyEvent) //for query app
//	AggregateLifecycle.apply(DepositCompletedEvent)// 사가 종료;; 근데 왜 로그 없지?
//요청 없어짐
		
cancelTransfer(17:01:33.744)
	취소 요청
	commandGateway.send(JejuBankCancelTransferCommand)
--------------------jeju
@CommandHandler on(JejuBankCancelTransferCommand)
	취소 요청
	AggregateLifecycle.apply(CompletedCancelTransferEvent) (17:01:38.818)
@EventSourcingHandler on(CompletedCancelTransferEvent) (17:01:38.819)
	잔액 복구 (17:01:38.819)
--------------------command	
@SagaEventHandler(associationProperty = "srcAccountID") on(CompletedCancelTransferEvent)
	계좌이체 취소완료 (17:01:38.854)
///사가 종료

 

  • 타임아웃이 났는데 어차피 잔액이 모자라서 진행이 안 되는 케이스

여기서 특이점은 jeju에서는 잔액이 증가(복구)하고 다시 취소(회수)하는 과정을 거치지만, command쪽은 아애 잔액 변경이 없다. 타임아웃으로 인한 복구가 먼저 진행되고, 잔액이 없어서 이체를 취소하는 요청 시 복구 중인지 확인한 후 복구 중이면 다시 복구를 취소하는 것이다(복구 중이 아니면 그대로 종료).

--------------------command
commandGateway.sendAndWait(MoneyTransferCommand)
@CommandHandler transferMoney(MoneyTransferCommand) (17:34:06.636)
	AggregateLifecycle.apply(MoneyTransferEvent)
///사가 시작
@StartSaga @SagaEventHandler(associationProperty = "transferID") on(MoneyTransferEvent)
	commandGateway.sendAndWait(JejuBankTransferCommand) (17:34:06.708)
	10초 기다리다 익셉션 (17:34:16.715)
cancelTransfer
	취소 요청
	commandGateway.send(JejuBankCancelTransferCommand) (17:34:16.715)
--------------------jeju
@CommandHandler on(JejuBankTransferCommand)
	15초 홀딩하던 도중 취소 요청을 받긴하지만 홀딩 후 성공처리 먼저함
	AggregateLifecycle.apply(TransferDeniedEvent) (17:34:21.832)
@CommandHandler on(JejuBankCancelTransferCommand) (17:34:21.862)
	AggregateLifecycle.apply(CompletedCancelTransferEvent)
@EventSourcingHandler on(CompletedCancelTransferEvent)
	잔액 증가/복구됨 (17:34:21.862)
--------------------command
@SagaEventHandler(associationProperty = "srcAccountID") on(TransferDeniedEvent) (17:34:21.874)
	취소 중이면서 계좌이체도 실패 -> 잔액 복구되면 안됨
	commandGateway.send(JejuBankCompensationCancelCommand) (17:34:21.874)
--------------------jeju
@CommandHandler on(JejuBankCompensationCancelCommand) (17:34:21.896)
	apply(CompletedCompensationCancelEvent)
 @EventSourcingHandler on(CompletedCompensationCancelEvent)
	잔액 다시 감소(17:34:21.900)
--------------------command
@SagaEventHandler(associationProperty = "srcAccountID") @EndSaga on(CompletedCompensationCancelEvent)
	상황 종료 (17:34:21.923)
///사가 종료

 

와 드디어.. axon framework clone coding 20강의 여정이 끝났다. 너무 어렵고 이게 지금 당장 쓰일 수 있을 것 같지 않아서 중간에 그만두고 싶기도 한데, 어쨌든 끝나서 후련하다!!

다음 시간에는 axon framework를 (개인적으로) 총 정리해볼 예정이다.

2022.02.21 - [개발/spring] - [axon] clone coding 후기

 

[axon] clone coding 후기

들어가며.. 마이크로 서비스 아키텍처(MSA) 프로젝트를 개발 및 운영을 하다보면 자연스레 도메인 모델은 복잡해지고 점점 설계 시점의 의도와는 다른 방향으로 변질되는 일이 빈번히 발생한다.

bangpurin.tistory.com

 

728x90
반응형

'개발 > axon framework' 카테고리의 다른 글

[axon] clone coding 후기  (0) 2022.02.21
[axon] saga (1) - clone coding  (0) 2022.02.18
[axon] query handler(2) - clone coding  (0) 2022.02.14
[axon] query handler(1) - clone coding  (0) 2022.02.11
[axon] event upcasting - clone coding  (0) 2022.02.04
반응형

이전 글: 2022.02.14 - [개발/spring] - [axon] query handler(2) - clone coding

 

[axon] query handler(2) - clone coding

이전 글: 2022.02.11 - [개발/spring] - [axon] query handler - clone coding [axon] query handler - clone coding 이전 글: 2022.02.04 - [개발/spring] - [axon] event upcasting - clone coding [axon] eve..

bangpurin.tistory.com

클론 코딩 참고 블로그는 다음과 같다: https://cla9.tistory.com/23?category=814447 

 

19. Saga 패턴을 활용한 트랜잭션 관리 - 2

1. 서론 이번 포스팅은 분산 트랜잭션 제어를 위한 Saga 패턴을 구현하겠습니다. 보상 트랜잭션까지 같이 구현하면 내용이 복잡하므로 보상 트랜잭션 및 Deadline 기능은 다음 포스팅에서 다루겠습

cla9.tistory.com

 

 

jeju모듈을 state stored aggregate 방식으로 전환 시 추가로 설정해야 하는 부분이 있다.

1. postgres에 jeju 계정을 생성하고 권한을 부여한다.

# su - postgres 

$ psql 
postgres=# create user jeju with password 'jeju'; 
postgres=# create database jeju owner jeju; 
postgres=# ALTER ROLE jeju WITH createdb; 
postgres=# GRANT ALL PRIVILEGES ON DATABASE jeju TO jeju; 
postgres=# \q

2. 혹시 진행 시 아래와 같은 에러를 만나면..

connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: 치명적오류:  사용자 "jeju"의 peer 인증을 실패했습니다.

아래와 같이 설정을 수정하자.

vi /var/lib/pgsql/14/data/pg_hba.conf vi /var/lib/pgsql/14/data/pg_hba.conf 
//아래와 같이 수정(제주 추가)

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             command, query, jeju                    md5
...

설정 후 디비 재시작 해야한다.

systemctl restart postgresql-14

 

command 애플리케이션에 @Saga 어노테이션이 활성화되니까 아래와 같이 axon server에 계속 쿼리를 날린다.

Hibernate: update token_entry set timestamp=? where processor_name=? and segment=? and owner=?

POST localhost:9091/account
Content-Type: application/json

{
	"accountID" : "test",
	"balance" : 100
}

위 api를 쏘고 jeju 애플리케이션을 관찰해본다.

14:02:24.357 DEBUG 11951 --- [mandProcessor-0] o.a.a.c.command.AxonServerCommandBus : Dispatch command [com.cqrs.jeju.command.AccountCreationCommand] locally
14:02:24.358 DEBUG 11951 --- [mandProcessor-0] o.a.commandhandling.SimpleCommandBus : Handling command [com.cqrs.jeju.command.AccountCreationCommand]
14:02:24.361 DEBUG 11951 --- [mandProcessor-0] o.a.m.unitofwork.AbstractUnitOfWork  : Starting Unit Of Work
---- for phase PREPARE_COMMIT
14:02:24.383 DEBUG 11951 --- [mandProcessor-0] com.cqrs.jeju.aggregate.Account      : >>> handling AccountCreationCommand(accountID=test-sender2, balance=1000)
14:02:24.389 DEBUG 11951 --- [mandProcessor-0] com.cqrs.jeju.aggregate.Account      : >>> event AccountCreationEvent(accountID=test-sender2, balance=1000)

Hibernate: insert into account (balance, accountid) values (?, ?)

AxonServerCommandBus 가 기본 command bus이기 때문에 command를 dispatch 하는데.. locally 하게 dispatch 하면 SimpleCommandBus가 핸들링하는가 보다.. 나중에 확인해봐야 하는 포인트

axon framework 에는 UnitOfWork라는 일의 단위가 있는데, 메시지를 처리하는 과정 중 수행해야 하는 액션을 조정하기 위해서 사용된다. UnitOfWork에는 여러 단계가 있고 보통 prepare commit 단계에 액션을 수행하는 것을 볼 수 있다.

또한 로그를 보면 CommandHandler가 수행하여 이벤트를 publish 한 후 EventSourcingHandler가 다시 받아서 처리하는 것을 알 수 있다.


POST http://localhost:8080/transfer
Content-Type: application/json

{
	"srcAccountID" : "test-sender2",
	"dstAccountID" : "5c3bb536-217b-45f1-adbf-edcc3f0a6c05",
	"amount" : 30,
	"bankType" : "JEJU"
}

위 전송 api를 쏘고 흐름을 파악하다가 멘붕이 왔다..

commandApplication jejuApplication queryApplication
transferServiceImpl
commandGateway(MoneyTransferCommand) 

@CommandHandler transferMoney(MoneyTransferCommand):MoneyTransferEvent 

@StartSaga @SagaEventHandler(associationProperty = "transferID") on(MoneyTransferEvent) 

commandGateway(JejuBankTransferCommand)
   
  @CommandHandler on : TransferApprovedEvent

@EventSourcingHandler on(TransferApprovedEvent): 잔액 차감 
 
@SagaEventHandler(associationProperty = "srcAccountID") on(TransferApprovedEvent)

commandGateway(TransferApprovedCommand)

@CommandHandler transferMoney(TransferApprovedCommand): DepositMoneyEvent/DepositCompletedEvent

@EventSourcingHandler -> 잔액 증가, 이벤트 끝
   
    @EventHandler on(DepositMoneyEvent)

 

정리하자면 아래와 같이 흘러간다.

  • commandGateway.send -> @CommandHandler
  • AggregateLifecycle.apply -> @EventSourcingHandler (command)
  • AggregateLifecycle.apply -> @EventHandler (query)
  • SagaLifecycle.associateWith("srcAccountID", event.getSrcAccountID()) -> @SagaEventHandler(associationProperty = "srcAccountID")

 

여기서 멘붕이 왔는데, 두 가지 이유 때문이었다.

  1. 로그에 command 쪽에서 잔액 증가/balance 관련 로그가 찍히지 않았다.
  2. 나는 command의 잔액이 증가하면 command 테이블의 account 테이블에 증액이 될 것 같았는데 그렇지 않았다.

1번은 잘못된 코드가 들어 있었다. 예전에 실습에 event-sourced-aggregate방식 -> state-stored-aggregate방식으로 전환하는 부분이 있었는데 이걸 다시 event-sourced-aggregate방식으로 바꿔놓고 이 실습을 진행하는 것이었는데(정확히 말하면 그 부분은 번외라 실습 제외 코드였다), 나는 그걸 모르고 state-stored-aggregate방식에서 진행하다 보니 일부 로직이 빠져있었다. 어쩐지 그 후 강의에서도 몇 번 코드가 이상하다고 느꼈다. 우선 실습을 위해 다시 event-sourced-aggregate방식으로 롤백하여 진행했다.

2번도 사실 1번의 연장인데, 나는 최종 상태가 DB에 있을 것이라 생각했으나 event-sourced-aggregate방식이라 DB에 마지막 값을 저장하지 않고 file에 이벤트만 쌓아두는 방식이었기에 당연한 것이었다. query 쪽에서는 그 이벤트를 받아다가 mv_account에 저장하고 있었으니, 원본 마지막에 command를 확인하는 게 아니라 query.mv_account 테이블을 직접 조회해서 확인하는 것이었다..!

 

그래서 전반적인 로그를 보면, (70 -> 80으로 10 증가)

2022-02-18 10:59:39.104 DEBUG 66614 --- [mandProcessor-2] c.c.command.aggregate.AccountAggregate   : >>> handling MoneyTransferCommand(srcAccountID=test-sender, dstAccountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05, amount=10, transferID=6791dff3-1e06-4aa4-88f9-09de7c9c078e, bankType=JEJU)
2022-02-18 10:59:39.160 DEBUG 66614 --- [gerProcessor]-0] com.cqrs.command.saga.TransferManager    : Created saga instance
2022-02-18 10:59:39.160 DEBUG 66614 --- [gerProcessor]-0] com.cqrs.command.saga.TransferManager    : event : MoneyTransferEvent(dstAccountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05, srcAccountID=test-sender, amount=10, transferID=6791dff3-1e06-4aa4-88f9-09de7c9c078e, commandFactory=com.cqrs.command.transfer.TransferCommandFactory@2eea704c)
2022-02-18 10:59:39.160  INFO 66614 --- [gerProcessor]-0] com.cqrs.command.saga.TransferManager    : 계좌 이체 시작 : MoneyTransferEvent(dstAccountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05, srcAccountID=test-sender, amount=10, transferID=6791dff3-1e06-4aa4-88f9-09de7c9c078e, commandFactory=com.cqrs.command.transfer.TransferCommandFactory@2eea704c) 
2022-02-18 10:59:39.160 DEBUG 66614 --- [gerProcessor]-0] o.a.a.c.command.AxonServerCommandBus     : Dispatch command [com.cqrs.command.transfer.JejuBankTransferCommand] with callback
--------------------------- jeju application 으로 전파
Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
2022-02-18 10:59:39.175 DEBUG 66614 --- [gerProcessor]-0] o.a.m.saga.repository.jpa.JpaSagaStore   : Storing saga id 4683dbfe-c141-43b9-b446-7df2b0d1855d as <com.cqrs.command.saga.TransferManager><commandFactory><transferCommand class="com.cqrs.command.transfer.JejuBankTransferCommand"><srcAccountID>test-sender</srcAccountID><dstAccountID>5c3bb536-217b-45f1-adbf-edcc3f0a6c05</dstAccountID><amount>10</amount><transferID>6791dff3-1e06-4aa4-88f9-09de7c9c078e</transferID></transferCommand></commandFactory></com.cqrs.command.saga.TransferManager>
Hibernate: insert into saga_entry (revision, saga_type, serialized_saga, saga_id) values (?, ?, ?, ?)
Hibernate: insert into association_value_entry (association_key, association_value, saga_id, saga_type, id) values (?, ?, ?, ?, ?)
Hibernate: insert into association_value_entry (association_key, association_value, saga_id, saga_type, id) values (?, ?, ?, ?, ?)
2022-02-18 10:59:39.221 DEBUG 66614 --- [ault-executor-1] o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 83
2022-02-18 10:59:39.249 DEBUG 66614 --- [gerProcessor]-0] o.a.m.saga.repository.jpa.JpaSagaStore   : Loaded saga id [4683dbfe-c141-43b9-b446-7df2b0d1855d] of type [com.cqrs.command.saga.TransferManager]
2022-02-18 10:59:39.250  INFO 66614 --- [gerProcessor]-0] com.cqrs.command.saga.TransferManager    : 이체 금액 10 계좌 반영 요청 : TransferApprovedEvent(srcAccountID=test-sender, dstAccountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05, transferID=6791dff3-1e06-4aa4-88f9-09de7c9c078e, amount=10)
2022-02-18 10:59:39.252 DEBUG 66614 --- [gerProcessor]-0] o.a.m.saga.repository.jpa.JpaSagaStore   : Updating saga id 4683dbfe-c141-43b9-b446-7df2b0d1855d as <com.cqrs.command.saga.TransferManager><commandFactory><transferCommand class="com.cqrs.command.transfer.JejuBankTransferCommand"><srcAccountID>test-sender</srcAccountID><dstAccountID>5c3bb536-217b-45f1-adbf-edcc3f0a6c05</dstAccountID><amount>10</amount><transferID>6791dff3-1e06-4aa4-88f9-09de7c9c078e</transferID></transferCommand></commandFactory></com.cqrs.command.saga.TransferManager>
Hibernate: update saga_entry set serialized_saga=?, revision=? where saga_id=?
Hibernate: insert into association_value_entry (association_key, association_value, saga_id, saga_type, id) values (?, ?, ?, ?, ?)
Hibernate: select nextval ('hibernate_sequence')
2022-02-18 10:59:39.262 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : >>> handling TransferApprovedCommand(accountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05, amount=10, transferID=6791dff3-1e06-4aa4-88f9-09de7c9c078e)
2022-02-18 10:59:39.262 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : >>> applying DepositMoneyEvent(holderID=98228ee7-9571-4c52-8023-2137c889abd0, accountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05, amount=10)
2022-02-18 10:59:39.262 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : === balance 80
--------------------------- event sourced aggregate라서 다시 첨부터
2022-02-18 10:59:39.277 DEBUG 66614 --- [mandProcessor-3] o.a.a.c.event.axon.AxonServerEventStore  : Reading events for aggregate id 5c3bb536-217b-45f1-adbf-edcc3f0a6c05
2022-02-18 10:59:39.295 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : >>> applying AccountCreationEvent(holderID=98228ee7-9571-4c52-8023-2137c889abd0, accountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05)
2022-02-18 10:59:39.296 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : >>> applying DepositMoneyEvent(holderID=98228ee7-9571-4c52-8023-2137c889abd0, accountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05, amount=30)
2022-02-18 10:59:39.296 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : === balance 30
2022-02-18 10:59:39.307 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : >>> applying DepositMoneyEvent(holderID=98228ee7-9571-4c52-8023-2137c889abd0, accountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05, amount=30)
2022-02-18 10:59:39.308 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : === balance 60
2022-02-18 10:59:39.308 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : >>> applying DepositMoneyEvent(holderID=98228ee7-9571-4c52-8023-2137c889abd0, accountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05, amount=10)
2022-02-18 10:59:39.308 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : === balance 70
2022-02-18 10:59:39.313 DEBUG 66614 --- [ault-executor-1] o.a.a.c.e.AxonServerEventStoreClient     : Done request for 5c3bb536-217b-45f1-adbf-edcc3f0a6c05: 36ms, 12 events
2022-02-18 10:59:39.313 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : >>> applying DepositMoneyEvent(holderID=98228ee7-9571-4c52-8023-2137c889abd0, accountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05, amount=10)
2022-02-18 10:59:39.313 DEBUG 66614 --- [mandProcessor-3] c.c.command.aggregate.AccountAggregate   : === balance 80
2022-02-18 10:59:39.337  INFO 66614 --- [ault-executor-1] o.a.a.c.event.axon.AxonServerEventStore  : Snapshot created
--------------------------- snapshot
2022-02-18 10:59:39.366 DEBUG 66614 --- [gerProcessor]-0] o.a.m.saga.repository.jpa.JpaSagaStore   : Loaded saga id [4683dbfe-c141-43b9-b446-7df2b0d1855d] of type [com.cqrs.command.saga.TransferManager]
2022-02-18 10:59:39.367  INFO 66614 --- [gerProcessor]-0] com.cqrs.command.saga.TransferManager    : 계좌 이체 성공 : DepositCompletedEvent(accountID=5c3bb536-217b-45f1-adbf-edcc3f0a6c05, transferID=6791dff3-1e06-4aa4-88f9-09de7c9c078e)
Hibernate: delete from association_value_entry where saga_id=?
Hibernate: delete from saga_entry where saga_id=?

로그에서도 알 수 있지만, Command 모듈에서 MoneyTransferEvent Event가 발행되면 saga와 관련한 정보를 디비에 넣고(saga instance 생성) 수정하고 계속 불러오면서 확인하는 것을 알 수 있다. 또한 모든 작업이 끝나면 해당 데이터를 마지막에 삭제하는 것으로 마무리한다.


그래서!! 앞으로는 그 방식을 state-stored-aggregate방식으로 전환하는 작업을 해보도록 한다. 

1. axonConfig.java 의 모든 빈을 주석처리(event-sourced-aggregate의 snapshot 관련 설정이기에)

2. accountAggregate.java를 aggregate/entity 방식으로 다시 바꾸고 아래 함수 작성

@CommandHandler
protected void transferMoney(MoneyTransferCommand command){
    log.debug(">>> handling {}", command);
    //제주 -> command 기 때문에 잔액 검사 필요 없음
    AggregateLifecycle.apply(MoneyTransferEvent.builder()
            .srcAccountID(command.getSrcAccountID())
            .dstAccountID(command.getDstAccountID())
            .amount(command.getAmount())
            .commandFactory(command.getBankType().getCommandFactory(command))
            .transferID(command.getTransferID())
            .build());

}
@CommandHandler
protected void transferMoney(TransferApprovedCommand command){
    this.balance += command.getAmount();
    log.debug("=== balance {}", this.balance);
    AggregateLifecycle.apply(new DepositMoneyEvent(this.holder.getHolderID(), command.getAccountID(), command.getAmount()));
    AggregateLifecycle.apply(new DepositCompletedEvent(command.getAccountID(), command.getTransferID()));
}

3. holderAggregate.java 도 aggregate/entity 방식으로 전환

4. accountCreationCommand.java 도 aggregate/entity 방식으로 전환

5. holderRepository 부활, transactionServiceImpl도 기존 방식으로 전환

상세 소스는 깃허브에..

디비를 보면 command 에 150원 들어있고 그걸 projectiong한 query에도 150원 들어있어 있고, jeju에서는 150원이 차감된 것을 확인할 수 있다.

 

 


Saga 공식문서: https://docs.axoniq.io/reference-guide/axon-framework/sagas/associations

 

728x90
반응형

'개발 > axon framework' 카테고리의 다른 글

[axon] clone coding 후기  (0) 2022.02.21
[axon] saga (2) - clone coding  (0) 2022.02.18
[axon] query handler(2) - clone coding  (0) 2022.02.14
[axon] query handler(1) - clone coding  (0) 2022.02.11
[axon] event upcasting - clone coding  (0) 2022.02.04
반응형

이전 글: 2022.02.11 - [개발/spring] - [axon] query handler - clone coding

 

[axon] query handler - clone coding

이전 글: 2022.02.04 - [개발/spring] - [axon] event upcasting - clone coding [axon] event upcasting - clone coding 이전 글: 2022.01.25 - [개발/spring] - [axon] query/replay 성능개선 - clone coding [..

bangpurin.tistory.com

클론 코딩 참고 블로그는 다음과 같다: https://cla9.tistory.com/21?category=814447 

 

17. Query 어플리케이션 구현(Query) - 3

1. 서론 이번 포스팅에서는 Scatter-Gather Query를 구현하겠습니다. Scatter-Gather Query는 동일한 Query를 수행하는 Query Handler가 여러 App에 존재할 경우 모든 App에 Query를 요청하여 결과를 취합받아 최..

cla9.tistory.com

 

지난 시간에 이어 쿼리 핸들링에 대한 이야기이다.

총 세 가지 방법이 있는데 두 가지는 지난 시간에 코딩하였고 오늘은 마지막 방법인 Scatter-Gather Query를 구현한다.

위 블로그를 따라 구현하다보면 마지막 서버 실행 부분에 아래와 같이 circular reference 관련 에러가 난다.

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'org.axonframework.config.Configurer': Requested bean is currently in creation: Is there an unresolvable circular reference?

예전 글에서도 본 적 있는 에런데 아래와 같은 설정을 추가하면 된다. springboot2.6부터 circular reference 가 기본 설정에서 제외되었기 때문이다.

spring: 
  main:
    allow-circular-references: true

 

앱을 모두 실행하고 테스트 해봤을 때 로그를 확인해본다.

1. 쿼리 서비스 흐름

HolderAccountController.getAccountInfoScatterGather -> QueryServiceImpl.getAccountInfoScatterGather -> holderID / 잔액을 dto로 만들어서 제주/서울에게 넘긴 후 결과를 LoanLimitResult.class로 받아오라고 명령 -> 서울/제주의 QueryHandler 가 받아서 처리 -> Query Service에서는 두 곳에서의 응답이 다 오기를 기다리고 결과를 조합하여 리스트로 내려줌

Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
///////서울 제주로 요청 보냄

//////응답1 서울
15:57:34.506 DEBUG 8092 --- [ault-executor-1] o.a.a.c.query.AxonServerQueryBus         : Received query response [message_identifier: "204f7d62-8f12-4df7-881d-339387ab3fa2"
payload {
  type: "com.cqrs.loan.LoanLimitResult"
  data: "<com.cqrs.loan.LoanLimitResult><holderID>e5775054-4265-46ef-8116-297ac22f480d</holderID><bankName>SeoulBank</bankName><balance>7980</balance><loanLimit>11970</loanLimit></com.cqrs.loan.LoanLimitResult>"
}
meta_data {
  key: "traceId"
  value {
    text_value: "b540735f-c8a3-48da-96a7-9e90aa752966"
  }
}
meta_data {
  key: "correlationId"
  value {
    text_value: "b540735f-c8a3-48da-96a7-9e90aa752966"
  }
}
request_identifier: "b540735f-c8a3-48da-96a7-9e90aa752966"
]
//////응답2 제주
15:57:34.507 DEBUG 8092 --- [ault-executor-1] o.a.a.c.query.AxonServerQueryBus         : Received query response [message_identifier: "16efaa5a-e221-4740-85b2-ff5478d8ed4e"
payload {
  type: "com.cqrs.loan.LoanLimitResult"
  data: "<com.cqrs.loan.LoanLimitResult><holderID>e5775054-4265-46ef-8116-297ac22f480d</holderID><bankName>JejuBank</bankName><balance>7980</balance><loanLimit>9576</loanLimit></com.cqrs.loan.LoanLimitResult>"
}
meta_data {
  key: "traceId"
  value {
    text_value: "b540735f-c8a3-48da-96a7-9e90aa752966"
  }
}
meta_data {
  key: "correlationId"
  value {
    text_value: "b540735f-c8a3-48da-96a7-9e90aa752966"
  }
}
request_identifier: "b540735f-c8a3-48da-96a7-9e90aa752966"
]

2. 제주

15:57:34.464 DEBUG 8162 --- [ueryProcessor-0] c.c.jeju.component.AccountLoanComponent  : >>> handling LoanLimitQuery(holderID=e5775054-4265-46ef-8116-297ac22f480d, balance=7980)

3. 서울

15:57:34.464 DEBUG 8163 --- [ueryProcessor-0] c.c.s.component.AccountLoanComponent     : >>> handling LoanLimitQuery(holderID=e5775054-4265-46ef-8116-297ac22f480d, balance=7980)

 

결과 화면

 

 

728x90
반응형
반응형

이전 글: 2022.02.04 - [개발/spring] - [axon] event upcasting - clone coding

 

[axon] event upcasting - clone coding

이전 글: 2022.01.25 - [개발/spring] - [axon] query/replay 성능개선 - clone coding [axon] query/replay 성능개선 - clone coding 이전 글: 2022.01.24 - [개발/spring] - [axon] query/replay - clone coding..

bangpurin.tistory.com

클론 코딩 참고 블로그는 다음과 같다: https://cla9.tistory.com/20?category=814447 

 

16. Query 어플리케이션 구현(Query) - 2

1. 서론 이번 시간에는 Query 기능 중 Point to Point, Subscription 기능을 구현합니다. 또한, Query 결과를 보기 위하여 Client 화면을 간략하게 만들겠습니다. Client 화면은 크게 Point to Point Query와 Subs..

cla9.tistory.com

 

이번 글은 쿼리 핸들링에 관한 내용이다.

위 과정 중, 어노테이션 validation 부분이 스프링 버전업으로 빠져있어서 추가해주었다.

2022.02.11 - [개발/spring] - [annotation] NotNull NotBlank NonNull NotEmpty...

 

[annotation] NotNull NotBlank NonNull NotEmpty...

스프링에서 어노테이션 검증을 사용하면 별도의 로직없이 setter가 작용할 때(컨트롤러에 들어오기에도 전) 바로 변수 검증을 진행한다. 인입 로그 없이 에러가 나서 당혹스러울 수는 있지만 잘

bangpurin.tistory.com

 

1. Point to Point Query

api를 실행했을 때의 흐름을 살펴보면 HolderAccountController.getAccountInfo -> QueryServiceImpl.getAccountInfo -> queryGateway -> queryHandler로 가는 것을 알 수 있다.
여기를 어떻게 찾아가는지 확인하기 위해 아래와 같이 같은 request/response를 가지는 핸들러를 하나 더 만들어서 테스트를 해봤는데.. 

////on3 작동    
@QueryHandler
public HolderAccountSummary on3(AccountQuery query){
    log.debug(">>> handling fake {}", query);
    HolderAccountSummary res = new HolderAccountSummary();
    res.setName("test");
    return res;
}

@QueryHandler
public HolderAccountSummary one(AccountQuery query){
    log.debug(">>> handling queryHandler {}", query);
    return repository.findByHolderId(query.getHolderId()).orElse(null);
}
-----------------------------------
////on2 작동
@QueryHandler
public HolderAccountSummary on3(AccountQuery query){
    log.debug(">>> handling fake {}", query);
    HolderAccountSummary res = new HolderAccountSummary();
    res.setName("test");
    return res;
}

@QueryHandler
public HolderAccountSummary on2(AccountQuery query){
    log.debug(">>> handling queryHandler {}", query);
    return repository.findByHolderId(query.getHolderId()).orElse(null);
}

여러 번 이름을 바꿔서 실행해봤는데, 어째 이름의 alphabetical order.. 가 낮은 순(a-> b-> c..)으로 작동되는 것 같다. 

찾아보니 axon에서도 query handler 의 순서에 대해 명시해놓긴 했으나 명확한 기준이라고 하긴 애매하다.

1. On the actual instance level of the class hierarchy (as returned by this.getClass()), all annotated methods are evaluated
2.If one or more methods are found of which all parameters can be resolved to a value, the method with the most specific type is chosen and invoked
3.If no methods are found on this level of the class hierarchy, the super type is evaluated the same way
4.When the top level of the hierarchy is reached, and no suitable query handler is found, this query handling instance is ignored.

내가 이해한 바로는 1. request/response 타입에 맞는 핸들러인지를 먼저 확인하고, 2. 해당 핸들러가 복수개이면 더 하위 레벨/자세한(상속을 받았다거나) 쪽을 따르는 듯하다. 내가 짠 위 코드는 같은 형태의 핸들러가 복수개이지만 뎁스가 같아서 다른 기준으로 순서를 정했을 터인데... 알파벳순이 왠지 맞는 것 같다..

 

2. Subscription Query

api를 실행했을 때의 흐름을 살펴보면 HolderAccountController.getAccountInfoSubscription -> QueryServiceImpl.getAccountInfoSubscription -> flux를 이용하여 subscribe 하고 있다는 것을 알 수 있다.

event handler 중 바로 노티 받을 곳에서 emit을 하면 subscribe에서 받는 구조.

화면은 SSE(Server Sent Event) 방식으로 구현되어 있으며 EventSource 객체를 사용하였다.

최초에 ui의 조회 버튼으로 커낵션을 연결하면

queryResult.initialResult().subscribe(emitter::next);

위 로직으로 인해 화면에 현 상태가 화면에 먼저 뿌려지며, 그 후에는 버튼을 누르지 않아도 emit 된 값이 listen 중이던 flux 쪽으로 와서 doOnNext를 타고 ui로 간다. ui에서도 비동기를 받을 수 있는 EventSource객체로 url을 호출한지라 eventSource.onmessage 함수에서 자동으로 데이터를 가져와 화면에 뿌려준다. 조회는 한 번만 눌렀을 뿐인데 화면에 변한 값이 자동으로 보인다.(신기하다..)

이후 종료를 누르면 그 후 일어난 변화에 대해서는 자동으로 표시해주지 않는다.

//현 상태 확인
14:07:44.689 DEBUG 6007 --- [nio-9090-exec-1] com.cqrs.query.service.QueryServiceImpl  : >>> queryServiceImpl getAccountInfoSubscription handling AccountQuery(holderId=e5775054-4265-46ef-8116-297ac22f480d)
14:07:44.693 DEBUG 6007 --- [nio-9090-exec-1] o.a.a.c.query.AxonServerQueryBus         : Subscription Query requested with subscription Id [340e3830-c7f5-44b9-8f03-98ddb1722cca]
14:07:44.790 DEBUG 6007 --- [ault-executor-1] c.c.q.p.HolderAccountProjection          : >>> handling queryHandler AccountQuery(holderId=e5775054-4265-46ef-8116-297ac22f480d)
//10원 인출
14:08:04.926 DEBUG 6007 --- [sor[accounts]-1] c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=e5775054-4265-46ef-8116-297ac22f480d, accountID=fffb8fa2-94dd-4e84-b7fd-072d09d01d33, amount=10) , timestamp : 2022-02-11T05:08:04.842Z
14:08:04.932 DEBUG 6007 --- [sor[accounts]-1] c.c.q.p.HolderAccountProjection          : >>> getHolder : e5775054-4265-46ef-8116-297ac22f480d 
14:08:04.978 DEBUG 6007 --- [ault-executor-0] com.cqrs.query.service.QueryServiceImpl  : doOnNext : com.cqrs.query.entity.HolderAccountSummary@7d175f21, isCanceled false
//10원 인출
14:08:13.930 DEBUG 6007 --- [sor[accounts]-1] c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=e5775054-4265-46ef-8116-297ac22f480d, accountID=fffb8fa2-94dd-4e84-b7fd-072d09d01d33, amount=10) , timestamp : 2022-02-11T05:08:13.907Z
14:08:13.930 DEBUG 6007 --- [sor[accounts]-1] c.c.q.p.HolderAccountProjection          : >>> getHolder : e5775054-4265-46ef-8116-297ac22f480d 
14:08:13.943 DEBUG 6007 --- [ault-executor-1] com.cqrs.query.service.QueryServiceImpl  : doOnNext : com.cqrs.query.entity.HolderAccountSummary@358555d0, isCanceled false
//4000원 추가
14:08:34.520 DEBUG 6007 --- [sor[accounts]-1] c.c.q.p.HolderAccountProjection          : >>> projecting DepositMoneyEvent(holderID=e5775054-4265-46ef-8116-297ac22f480d, accountID=37ddb7c8-0d3b-443d-a6d5-c83d873f0521, amount=4000) , timestamp : 2022-02-11T05:08:34.499Z
14:08:34.521 DEBUG 6007 --- [sor[accounts]-1] c.c.q.p.HolderAccountProjection          : >>> getHolder : e5775054-4265-46ef-8116-297ac22f480d 
14:08:34.536 DEBUG 6007 --- [ault-executor-0] com.cqrs.query.service.QueryServiceImpl  : doOnNext : com.cqrs.query.entity.HolderAccountSummary@3cf7a219, isCanceled false

Subscription 방식은 한번 커넥션을 맺은 상태에서는 별다른 호출이 없어도 자동으로 변화를 감지하기 때문에 사용성이 좋을 것 같긴 하지만 webflux와 EventSource를 사용하면서 개발의 진입장벽이 있을 것으로 보인다. 또한 서버와 클라이언트의 Connection을 유지해야하기 때문에 Subscription Query가 증가할수록 그에 상응하는 Thread 수가 증가한다는 단점이 있다는 것을 확인해야 한다.

ui에서 별다른 유저 액션 없이도 어떻게 계속 동기화된 데이터를 받을 수 있을까를 고민했던 과거의 나에게 약간의 해답을 준 오늘의 실습이었다. 아직 모든 게 완벽히 와닿지는 않지만 여러 번 반복해서 보다 보면 조금씩 이해할 수 있지 않을까...

728x90
반응형
반응형

이전 글:

2022.01.25 - [개발/spring] - [axon] query/replay 성능개선 - clone coding

 

[axon] query/replay 성능개선 - clone coding

이전 글: 2022.01.24 - [개발/spring] - [axon] query/replay - clone coding [axon] query/replay - clone coding 2022.01.19 - [개발/spring] - [axon] state stored aggregate - clone coding [axon] state sto..

bangpurin.tistory.com

클론 코딩 참고 블로그는 다음와 같다: https://cla9.tistory.com/18?category=814447 

 

14. Query 어플리케이션 구현(Event) - 4

1. 서론 Software 개발 및 유지보수 단계에서 요구사항에 의하여 데이터 모델은 변하기 마련입니다. 그리고 바뀌는 데이터모델에 맞춰 Event 또한 형태가 변합니다. 이때, 이전 발행된 Event와 앞으로

cla9.tistory.com

 

entity -> event의 항목이 바뀌어서 event 모델의 혼용이 생길 경우(ex. replay) 구 -> 신 모델을 어떻게 반영해야 할지에 대해 코드에 알려줘야 할 필요가 있다. 이번 장은 그런 것에 관한 내용이다.

event versioning 이라고 불리는 이 행위는 axon에서는 event upcasting 이라는 용어로 불린다.

 

위 블로그를 참고로 개발하던 와중 필요한 dependency 가 있었다. xml 분석을 위한 라이브러리를 추가해준다.

implementation 'org.dom4j:dom4j:2.1.3'

 

테스트: 아래와 같이 company가 있는 정보로 호출하였다.

POST http://localhost:8080/holder
{
  "holderName" : "ch13",
  "tel" : "02-1234-5678",
  "address" : "OO시 OO구 heheqq",
  "company" : "konai"
}

 

1. command application

DEBUG 8895 --- [mandProcessor-1] c.c.command.aggregate.HolderAggregate    : handling HolderCreationCommand(holderID=e5775054-4265-46ef-8116-297ac22f480d, holderName=ch13, tel=02-1234-5678, address=OO시 OO구 heheqq, company=konai)
...
Hibernate: insert into holder (address, company, holder_name, tel, holder_id) values (?, ?, ?, ?, ?)

command

DB에 잘 들어간 것 확인

2. query application

DEBUG 8893 --- [sor[accounts]-1] c.c.q.p.HolderAccountProjection          : >>> projecting HolderCreationEvent(holderID=e5775054-4265-46ef-8116-297ac22f480d, holderName=ch13, tel=02-1234-5678, address=OO시 OO구 heheqq, company=konai) , timestamp : 2022-02-03T06:52:26.203Z
...
Hibernate: insert into mv_account (account_cnt, address, name, tel, total_balance, holder_id) values (?, ?, ?, ?, ?, ?)

HolderAccountSummary 클래스에 company를 추가한 것은 아니라서 mv_account 테이블에 projection 할 때 추가로 넣는건 없다.

 

3. replay 실행

replay를 실행하는데 문제가 생겼다.. repository를 못 찾아 nullPointException이 뜬 것이다..

di not found...

@Component
@RequiredArgsConstructor
@Slf4j
@ProcessingGroup("accounts")
@EnableRetry
public class HolderAccountProjection {
    private final AccountRepository repository;

...

    @EventHandler
    @AllowReplay
    protected void on(DepositMoneyEvent event, @Timestamp Instant instant){
        log.debug(">>> projecting {} , timestamp : {}", event, instant.toString());
        HolderAccountSummary holderAccount = getHolderAccountSummary(event.getHolderID());
        holderAccount.setTotalBalance(holderAccount.getTotalBalance() + event.getAmount());
        repository.save(holderAccount);
    }

...

    /**
     * 초기화 할 때 실행하는 부분
     */
    @ResetHandler
    private void resetHolderAccountInfo(){
        log.debug("reset triggered");
        repository.deleteAll();
    }

소스는 위와 같은데, 특이한 것은

  1. on 함수는 아무 문제 없이 실행된다(save 가능)
  2. @EnableRetry 어노테이션을 주석처리하면 reset도 에러없이 실행된다....
  3. on 함수 처럼 AllowReplay / DisallowReplay 어노테이션 등등을 넣어봤는데도 똑같다.

뭔가 ResetHandler랑 충돌난게 아닐까.. spring retry를 좀 더 파봐야 겠다..

++++ 해결... 왜인지는 아직 모르겠지만 public 함수로 바꾸니 정상적으로 된다... 공식문서 예시에는 모든 함수가 public으로 되어있긴 한데, 필수는 아닌 것 같은데.... 더 확인이 필요하다.

@ResetHandler
public void resetHolderAccountInfo(){
    log.debug("reset triggered");
    repository.deleteAll();
}

우선 enable retry 를 주석처리한 후 실행해본다. 

upcast bean(eventUpcasterChain 함수)을 등록 하기 전에 한 번, 후에 한 번 해보았다.

upcast bean 등록 전
DEBUG 9935 --- [sor[accounts]-0] c.c.q.p.HolderAccountProjection  : >>> projecting HolderCreationEvent(holderID=65cc77b7-6820-472d-ba72-dd942f801f21, holderName=melvin, tel=02-1234-5678, address=OO시 OO구 hehe, company=null) , timestamp : 2022-01-19T04:23:43.806Z
revision 1.0 version up
DEBUG 9935 --- [sor[accounts]-0] c.c.q.p.HolderAccountProjection  : >>> projecting HolderCreationEvent(holderID=4cafc63c-cdcb-4acf-8943-8dee7492383b, holderName=ch13, tel=02-1234-5678, address=OO시 OO구 heheqq, company=null) , timestamp : 2022-02-03T06:50:28.748Z
DEBUG 9935 --- [sor[accounts]-0] c.c.q.p.HolderAccountProjection  : >>> projecting HolderCreationEvent(holderID=e5775054-4265-46ef-8116-297ac22f480d, holderName=ch13, tel=02-1234-5678, address=OO시 OO구 heheqq, company=konai) , timestamp : 2022-02-03T06:52:26.203Z

upcast bean 등록 후
DEBUG 10126 --- [sor[accounts]-1] c.c.q.p.HolderAccountProjection  : >>> projecting HolderCreationEvent(holderID=65cc77b7-6820-472d-ba72-dd942f801f21, holderName=melvin, tel=02-1234-5678, address=OO시 OO구 hehe, company=N/A) , timestamp : 2022-01-19T04:23:43.806Z
revision 1.0 version up
DEBUG 10126 --- [sor[accounts]-1] c.c.q.p.HolderAccountProjection  : >>> projecting HolderCreationEvent(holderID=4cafc63c-cdcb-4acf-8943-8dee7492383b, holderName=ch13, tel=02-1234-5678, address=OO시 OO구 heheqq, company=null) , timestamp : 2022-02-03T06:50:28.748Z
DEBUG 10126 --- [sor[accounts]-1] c.c.q.p.HolderAccountProjection  : >>> projecting HolderCreationEvent(holderID=e5775054-4265-46ef-8116-297ac22f480d, holderName=ch13, tel=02-1234-5678, address=OO시 OO구 heheqq, company=konai) , timestamp : 2022-02-03T06:52:26.203Z

확인사항

  • 빈 등록 전에는 version up 전의 company 정보가 null로 나온다.
  • 빈 등록 후에는 version up 전의 company 정보가 N/A로 나온다.
  • event version up 후 실수로 company 정보를 넣지 않고 post api 를 요청했는데, 빈 등록 전/후 모두 null로 나왔다. 버전 업을 하고 나서 null로 보냈기 때문에 빈의 유무와 상관없이 null로 나오는 것이 맞다.

 


참고: event upcasting 원문, 기타 의미: https://docs.axoniq.io/reference-guide/axon-framework/events/event-versioning

 

Event Versioning - Axon Reference Guide

Axon's upcasters do not work with the EventMessage directly, but with an IntermediateEventRepresentation. The IntermediateEventRepresentation provides functionality to retrieve all necessary fields to construct an EventMessage (and thus a upcasted EventMes

docs.axoniq.io

 

728x90
반응형
반응형

이전 글: 2022.01.24 - [개발/spring] - [axon] query/replay - clone coding

 

[axon] query/replay - clone coding

2022.01.19 - [개발/spring] - [axon] state stored aggregate - clone coding [axon] state stored aggregate - clone coding 2022.01.18 - [개발/spring] - [axon] snapshot - clone coding [axon] snapshot - c..

bangpurin.tistory.com

클론 코딩 참고 블로그는 다음와 같다: https://cla9.tistory.com/17?category=814447 

 

13. Query 어플리케이션 구현(Event) - 3

1. 서론 이전 포스팅에서 Replay에 대해서 학습했습니다. Replay는 신규 Read Model이 추가되거나 기존 모델의 변경이 있을 때, EventStore에서 기존 내역을 전달받아 재수행하는 작업입니다. 따라서 EventSo

cla9.tistory.com

 

지난 시간, replay(초기화 후 이벤트 다시 재생) 시 성능 이슈에 대해 잠깐 언급했는데, 이번 시간에 개선 포인트에 대해 생각해본다.

1. batch size

프로젝트에 아래와 같이 설정되어 있거나 없다면(기본값이 tracking) event processor 중 tracking 모드를 사용 중이다.

axon:
  eventhandling:
    processors:
      name:
        mode: tracking

위 내용은 아래와 같이 스프링 빈으로 등록하여 추가적인 설정을 할 수도 있다. 

배치사이즈 변경, 초기 세그먼트 수 세팅, 스레드 팩토리 지정, 단일스레드 설정, 병렬처리 시 스레드 수 지정 등.. 관련 초기값 및 설정 방법은 아래와 같이 빈을 생성하고 제공되는 함수를 통해 여러 값을 변경하면 된다.

@Configuration
public class AxonConfig {

    @Autowired
    public void configure(EventProcessingConfigurer eventProcessingConfigurer){
        eventProcessingConfigurer.registerTrackingEventProcessor(
               "accounts",
                org.axonframework.config.Configuration::eventStore,
                configuration -> TrackingEventProcessorConfiguration
                        .forSingleThreadedProcessing()
                        .andBatchSize(100) //default 1
        );
    }
}

우선 싱글스레드로 설정하였고 배치 사이즈를 100으로 조정하였다.

여기서 name(위 예시에서 "accounts")을 주어야 하는데, processingGroup의 이름을 넣으면 된다. 이게 뭐냐고..?

사실 저번 시간에 reset 함수를 구현할 때 tracking mode 에 대한 설정을 했었는데..

@Override
public void reset() {
    configuration.eventProcessingConfiguration()
            .eventProcessorByProcessingGroup("accounts", TrackingEventProcessor.class)
            .ifPresent(trackingEventProcessor -> {
                trackingEventProcessor.shutDown();
                trackingEventProcessor.resetTokens(); // 토큰초기화
                trackingEventProcessor.start();
            });
}

여기서 주었던 이름과 processing type 에 힌트가 있었다.

그렇다면 어떤 값들을 바꿀 수 있는건가?

참고로 AbstractEventProcessor 구현체로 TrackingEventProcessor와 SubscribingEventProcessor가 있는데, 우리는 tracking모드로 구현하였으니 TrackingEventProcessor를 사용한다. 그리고 TrackingEventProcessor가 사용하는 설정 값은 TrackingEventProcessorConfiguration 에 있다.

기본값은 아래와 같다.

  • Thread 수 : 1개
  • Batch Size : 1
  • 최대 Thread 수 : Segment 개수
  • TokenClaim 주기 : 5000ms
TrackingEventProcessorConfiguration.java

private static final int DEFAULT_BATCH_SIZE = 1;
private static final int DEFAULT_THREAD_COUNT = 1;
//단위 ms
private static final int DEFAULT_TOKEN_CLAIM_INTERVAL = 5000;
private int eventAvailabilityTimeout = 1000;

private TrackingEventProcessorConfiguration(int numberOfSegments) {
    this.batchSize = DEFAULT_BATCH_SIZE;
    this.initialSegmentCount = numberOfSegments;
    this.maxThreadCount = numberOfSegments;
    this.threadFactory = pn -> new AxonThreadFactory("EventProcessor[" + pn + "]");
    this.tokenClaimInterval = DEFAULT_TOKEN_CLAIM_INTERVAL;
}

위 클래스에 정의된 함수를 사용하여 설정 값을 변경하면 된다.

 

2. 병렬처리

성능 향상을 고민할 때, 보통 생각하는 방법이 병렬 처리 기법이다. 허나 여기서 병렬로 처리 시 이벤트의 순서가 꼬이게 될 가능성이 있는데, axon에서는 이를 위해 sequencing 정책을 제공한다. 위 설정에서 단일 스레드를 병렬로 변경하고 스레드 갯수를 지정함다.

@Configuration
public class AxonConfig {

    @Autowired
    public void configure(EventProcessingConfigurer eventProcessingConfigurer){
        eventProcessingConfigurer.registerTrackingEventProcessor(
          "accounts",
                org.axonframework.config.Configuration::eventStore,
                configuration -> TrackingEventProcessorConfiguration
                        .forParallelProcessing(3) ////
                        .andBatchSize(100)
        );

        eventProcessingConfigurer.registerSequencingPolicy(
            "accounts",
                configuration -> SequentialPerAggregatePolicy.instance() ////
        );
    }
}

하지만 이렇게 해도 aggregate가 다르면 다른 스레드에서 실행될 수 있어 (예를 들어) 계좌 생성 이전에 잔액 조회가 실행되어 에러가 날 수 있다.

이를 위해 실패 시 자동으로 재시도하게 하는 spring-retry를 사용한다. 

implementation 'org.springframework.retry:spring-retry'
 
 //service @EnableRetry 추가
@EnableRetry
@Component
@RequiredArgsConstructor
@Slf4j
@ProcessingGroup("accounts")
public class HolderAccountProjection {
 
 //method @Retryable 추가; 언제(에러발생 시) / 몇 번 시도(5회) / 언제 시도(1초 후)
@Retryable(value = {NoSuchElementException.class}, maxAttempts = 5, backoff = @Backoff(delay = 1000))
@EventHandler
@AllowReplay
protected void on(AccountCreationEvent event, @Timestamp Instant instant){

 

기타 포인트는 클론 코딩 참고 블로그에 자세히 기술되어 있어 읽어보면 되겠다.

728x90
반응형
반응형

2022.01.19 - [개발/spring] - [axon] state stored aggregate - clone coding

 

[axon] state stored aggregate - clone coding

2022.01.18 - [개발/spring] - [axon] snapshot - clone coding [axon] snapshot - clone coding 2022.01.12 - [개발/spring] - [axon] difficulties while clone coding [axon] difficulties while clone coding..

bangpurin.tistory.com

 

저번 글에 이어서 드디어 query application 을 개발한다.

 클론 코딩 참고 블로그는 다음와 같다: https://cla9.tistory.com/16?category=814447 

 

12. Query 어플리케이션 구현(Event) - 2

1. 서론 이번 시간에는 Query App의 EventHandler 로직을 구현하도록 하겠습니다. Query App의 Read Model은 이전 포스팅에서 도출한 구조를 사용하도록 하겠습니다. 2. Projection 구현 Command에서 발생된 Event..

cla9.tistory.com

 

어플리케이션을 실행하니까, hibernate log가 난리난다.. 밀린 모든 이벤트에 대한 프로젝션을 진행해서인 듯

Hibernate: create table mv_account (holder_id varchar(255) not null, account_cnt int8 not null, address varchar(255) not null, name varchar(255) not null, tel varchar(255) not null, total_balance int8 not null, primary key (holder_id))

Hibernate: select tokenentry0_.segment as col_0_0_ from token_entry tokenentry0_ where tokenentry0_.processor_name=? order by tokenentry0_.segment ASC
Hibernate: select tokenentry0_.segment as col_0_0_ from token_entry tokenentry0_ where tokenentry0_.processor_name=? order by tokenentry0_.segment ASC

Hibernate: select tokenentry0_.processor_name as processo1_3_0_, tokenentry0_.segment as segment2_3_0_, tokenentry0_.owner as owner3_3_0_, tokenentry0_.timestamp as timestam4_3_0_, tokenentry0_.token as token5_3_0_, tokenentry0_.token_type as token_ty6_3_0_ from token_entry tokenentry0_ where tokenentry0_.processor_name=? and tokenentry0_.segment=? for update
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?

... 반복
Hibernate: select tokenentry0_.processor_name as processo1_3_0_, tokenentry0_.segment as segment2_3_0_, tokenentry0_.owner as owner3_3_0_, tokenentry0_.timestamp as timestam4_3_0_, tokenentry0_.token as token5_3_0_, tokenentry0_.token_type as token_ty6_3_0_ from token_entry tokenentry0_ where tokenentry0_.processor_name=? and tokenentry0_.segment=? for update
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: select holderacco0_.holder_id as holder_i1_1_0_, holderacco0_.account_cnt as account_2_1_0_, holderacco0_.address as address3_1_0_, holderacco0_.name as name4_1_0_, holderacco0_.tel as tel5_1_0_, holderacco0_.total_balance as total_ba6_1_0_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: insert into mv_account (account_cnt, address, name, tel, total_balance, holder_id) values (?, ?, ?, ?, ?, ?)

그냥 띄우기만 해도 select for update 96번; update 96번; insert 7번

o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 45

위 로그가 0~45 까지 두 바퀴 돈 것으로 보아 아마 액션이 45번이었을 것 같고, 그놈의 결과를 aggregateId별로 group by하니까 아래와 같이 7건이 나온 것 같다.

 

헉 그런데 더 놀라운건, 서버를 단지 띄우기만 했는데도 아래의 쿼리문이 매 초 2번씩 실행된다는 것이다..

Hibernate: update token_entry set timestamp=? where processor_name=? and segment=? and owner=?

찾아보니 이벤트 갱신내용 확인하려고 그런다는 것 같다.

https://stackoverflow.com/questions/68209494/why-is-token-entry-constantly-updated-when-using-saga-of-axon-framework

 

Why is 'token_entry' constantly updated when using saga of axon framework?

I made up project with spring boot & jpa(mysql) & axon framework. (I used only the Axon auto configuration.) And, I wrote the sagas of the axon framework as below. @Saga class OrderSagaMana...

stackoverflow.com

 

 


replay 란 토큰 정보를 초기화하여 처음부터 혹은 일정 시점부터 토큰을 다 지우고 새롭게 넣는 방법이다.

이번 실습에서는 전체 초기화 후 다시 넣는 방법을 사용한다. 위 블로그대로 구현하고 실행하면 아래와 같은 로그가 지나간다(일부 발췌).

// service reset 실행
//  1. trackingEventProcessor.shutDown();
//	2. trackingEventProcessor.resetTokens(); // 토큰초기화
//	3. trackingEventProcessor.start();

o.a.e.TrackingEventProcessor             : Shutdown state set for Processor 'accounts'.
o.a.e.TrackingEventProcessor             : Processor 'accounts' awaiting termination...

o.a.a.c.u.FlowControllingStreamObserver  : Observer stopped
o.a.e.TrackingEventProcessor             : Released claim
o.a.e.TrackingEventProcessor             : Worker for segment Segment[0/0] stopped.

c.c.q.p.HolderAccountProjection          : reset triggered
Hibernate: delete from mv_account where holder_id=?
Hibernate: delete from mv_account where holder_id=?
Hibernate: delete from mv_account where holder_id=?
Hibernate: delete from mv_account where holder_id=?
Hibernate: delete from mv_account where holder_id=?
Hibernate: delete from mv_account where holder_id=?
Hibernate: delete from mv_account where holder_id=?
o.a.e.TrackingEventProcessor             : Worker assigned to segment Segment[0/0] for processing
o.a.e.TrackingEventProcessor             : Using current Thread for last segment worker: TrackingSegmentWorker{processor=accounts, segment=Segment[0/0]}

o.a.e.TrackingEventProcessor             : Fetched token: ReplayToken{currentToken=IndexTrackingToken{globalIndex=-1}, tokenAtReset=IndexTrackingToken{globalIndex=45}} for segment: Segment[0/0]
o.a.a.c.event.axon.AxonServerEventStore  : open stream: 0
o.a.a.c.u.FlowControllingStreamObserver  : Sending response to AxonServer platform, remaining permits: 2500
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 0
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 1
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 2
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 3
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 4
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 5
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 6
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 7
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 8
o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.UnitOfWork$$Lambda$1477/0x0000000800a2b040 for phase COMMIT
o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.UnitOfWork$$Lambda$1479/0x0000000800a2a840 for phase ROLLBACK
o.a.m.unitofwork.AbstractUnitOfWork      : Starting Unit Of Work
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 9
o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.AbstractUnitOfWork$$Lambda$1482/0x0000000800a2a440 for phase ROLLBACK
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase STARTED
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 10
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 11
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 12
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 13
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 14
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 15
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 16
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 17
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 18
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 19
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 20
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 21
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 22
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 23
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 24
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 25
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 26
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 27
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 28
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 29
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 30
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 31
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 32
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 33
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 34
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 35
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 36
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 37
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 38
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 39
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 40
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 41
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 42
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 43
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 44
o.a.a.c.event.axon.AxonServerEventStore  : Received event with token: 45
o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.TrackingEventProcessor$$Lambda$1488/0x0000000800a28c40 for phase PREPARE_COMMIT
c.c.q.p.HolderAccountProjection          : >>> projecting HolderCreationEvent(holderID=9c53f4b2-9898-457f-a050-59dd012c8ddd, holderName=kevin, tel=02-1234-5678, address=OO시 OO구) , timestamp : 2022-01-13T06:45:47.273Z
o.a.m.unitofwork.AbstractUnitOfWork      : Committing Unit Of Work
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase PREPARE_COMMIT
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase COMMIT
Hibernate: insert into mv_account (account_cnt, address, name, tel, total_balance, holder_id) values (?, ?, ?, ?, ?, ?)
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase AFTER_COMMIT
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase CLEANUP
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase CLOSED
//1-cycle example
o.a.m.unitofwork.AbstractUnitOfWork      : Starting Unit Of Work
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase STARTED
Hibernate: select tokenentry0_.processor_name as processo1_3_0_, tokenentry0_.segment as segment2_3_0_, tokenentry0_.owner as owner3_3_0_, tokenentry0_.timestamp as timestam4_3_0_, tokenentry0_.token as token5_3_0_, tokenentry0_.token_type as token_ty6_3_0_ from token_entry tokenentry0_ where tokenentry0_.processor_name=? and tokenentry0_.segment=? for update
c.c.q.p.HolderAccountProjection          : >>> projecting HolderCreationEvent(holderID=a00b1e82-252a-4d14-bc76-11a633514ece, holderName=kevin, tel=02-1234-5678, address=OO시 OO구) , timestamp : 2022-01-13T08:26:01.301Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_0_, holderacco0_.account_cnt as account_2_1_0_, holderacco0_.address as address3_1_0_, holderacco0_.name as name4_1_0_, holderacco0_.tel as tel5_1_0_, holderacco0_.total_balance as total_ba6_1_0_ from mv_account holderacco0_ where holderacco0_.holder_id=?
o.a.m.unitofwork.AbstractUnitOfWork      : Committing Unit Of Work
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase PREPARE_COMMIT
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase COMMIT
Hibernate: insert into mv_account (account_cnt, address, name, tel, total_balance, holder_id) values (?, ?, ?, ?, ?, ?)
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase AFTER_COMMIT
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase CLEANUP
o.a.m.u.MessageProcessingContext         : Notifying handlers for phase CLOSED
//45 loop
c.c.q.p.HolderAccountProjection          : >>> projecting HolderCreationEvent(holderID=5cfc5832-d1b8-4f73-bf4f-2afa3cf7ffa4, holderName=kevin, tel=02-1234-5678, address=OO시 OO구) , timestamp : 2022-01-13T08:37:00.461Z
Hibernate: insert into mv_account (account_cnt, address, name, tel, total_balance, holder_id) values (?, ?, ?, ?, ?, ?)
c.c.q.p.HolderAccountProjection          : >>> projecting HolderCreationEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, holderName=kevin, tel=02-1234-5678, address=OO시 OO구) , timestamp : 2022-01-18T03:06:26.829Z
Hibernate: insert into mv_account (account_cnt, address, name, tel, total_balance, holder_id) values (?, ?, ?, ?, ?, ?)
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
c.c.q.p.HolderAccountProjection          : >>> projecting AccountCreationEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621) , timestamp : 2022-01-18T03:08:11.008Z
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting DepositMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=300) , timestamp : 2022-01-18T03:08:50.248Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.TrackingEventProcessor$$Lambda$1488/0x0000000800a28c40 for phase PREPARE_COMMIT
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T03:09:47.116Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T03:09:50.161Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T03:09:56.770Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T03:16:16.136Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting HolderCreationEvent(holderID=4eec3a20-efe0-4264-bbb5-ac3233c49960, holderName=kevin, tel=02-1234-5678, address=OO시 OO구) , timestamp : 2022-01-18T05:12:55.827Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_0_, holderacco0_.account_cnt as account_2_1_0_, holderacco0_.address as address3_1_0_, holderacco0_.name as name4_1_0_, holderacco0_.tel as tel5_1_0_, holderacco0_.total_balance as total_ba6_1_0_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: insert into mv_account (account_cnt, address, name, tel, total_balance, holder_id) values (?, ?, ?, ?, ?, ?)
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
c.c.q.p.HolderAccountProjection          : >>> projecting AccountCreationEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=d1726ff3-ee98-4262-8232-bae85ad26b27) , timestamp : 2022-01-18T05:13:07.173Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting DepositMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=300) , timestamp : 2022-01-18T05:13:18Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T05:13:53.745Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T05:14:34.937Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T05:18:44.814Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T05:24:47.280Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T05:25:33.178Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T05:55:17.833Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T05:55:30.381Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T05:57:29.647Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10) , timestamp : 2022-01-18T06:26:39.681Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting HolderCreationEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, holderName=melvin, tel=02-1234-5678, address=OO시 OO구 hehe) , timestamp : 2022-01-18T06:54:22.558Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_0_, holderacco0_.account_cnt as account_2_1_0_, holderacco0_.address as address3_1_0_, holderacco0_.name as name4_1_0_, holderacco0_.tel as tel5_1_0_, holderacco0_.total_balance as total_ba6_1_0_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: insert into mv_account (account_cnt, address, name, tel, total_balance, holder_id) values (?, ?, ?, ?, ?, ?)
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
c.c.q.p.HolderAccountProjection          : >>> projecting AccountCreationEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb) , timestamp : 2022-01-18T06:54:32.705Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting DepositMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=400) , timestamp : 2022-01-18T06:54:57.181Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T06:55:24.383Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T06:55:36.025Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T06:55:47.363Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T07:02:42.397Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T07:06:09.120Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting DepositMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=400) , timestamp : 2022-01-18T07:06:10.742Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T07:06:15.558Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T07:06:16.857Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting AccountCreationEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=0041f839-4ea4-42bf-94ca-6ebcbdaaf2ee) , timestamp : 2022-01-18T07:15:32.990Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T07:16:07.264Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting DepositMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=400) , timestamp : 2022-01-18T07:16:28.820Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T07:16:47.148Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T07:46:04.953Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T07:46:59.301Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10) , timestamp : 2022-01-18T08:11:22.699Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting HolderCreationEvent(holderID=65cc77b7-6820-472d-ba72-dd942f801f21, holderName=melvin, tel=02-1234-5678, address=OO시 OO구 hehe) , timestamp : 2022-01-19T04:23:43.806Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_0_, holderacco0_.account_cnt as account_2_1_0_, holderacco0_.address as address3_1_0_, holderacco0_.name as name4_1_0_, holderacco0_.tel as tel5_1_0_, holderacco0_.total_balance as total_ba6_1_0_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: insert into mv_account (account_cnt, address, name, tel, total_balance, holder_id) values (?, ?, ?, ?, ?, ?)
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
c.c.q.p.HolderAccountProjection          : >>> projecting AccountCreationEvent(holderID=65cc77b7-6820-472d-ba72-dd942f801f21, accountID=fffb8fa2-94dd-4e84-b7fd-072d09d01d33) , timestamp : 2022-01-19T05:52:19.493Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting DepositMoneyEvent(holderID=65cc77b7-6820-472d-ba72-dd942f801f21, accountID=fffb8fa2-94dd-4e84-b7fd-072d09d01d33, amount=400) , timestamp : 2022-01-19T06:16:23.159Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting AccountCreationEvent(holderID=65cc77b7-6820-472d-ba72-dd942f801f21, accountID=38886d53-ad4c-4bfe-b61a-059948f4a2f5) , timestamp : 2022-01-19T06:25:34.896Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=65cc77b7-6820-472d-ba72-dd942f801f21, accountID=fffb8fa2-94dd-4e84-b7fd-072d09d01d33, amount=150) , timestamp : 2022-01-19T06:26:16.604Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?
c.c.q.p.HolderAccountProjection          : >>> projecting WithdrawMoneyEvent(holderID=65cc77b7-6820-472d-ba72-dd942f801f21, accountID=fffb8fa2-94dd-4e84-b7fd-072d09d01d33, amount=10) , timestamp : 2022-01-19T07:04:16.234Z
Hibernate: select holderacco0_.holder_id as holder_i1_1_, holderacco0_.account_cnt as account_2_1_, holderacco0_.address as address3_1_, holderacco0_.name as name4_1_, holderacco0_.tel as tel5_1_, holderacco0_.total_balance as total_ba6_1_ from mv_account holderacco0_ where holderacco0_.holder_id=?
Hibernate: update token_entry set owner=?, timestamp=?, token=?, token_type=? where processor_name=? and segment=?
Hibernate: update mv_account set account_cnt=?, address=?, name=?, tel=?, total_balance=? where holder_id=?

로그를 보니 디비 내용물을 다 지우고 처음부터 다시 넣는 것을 알 수 있으며, 디비 값을 조회했을 때 이전과 똑같은 결과가 나오는 것을 확인할 수 있다.

근데 왜 replay를 query 쪽에서 구현하는걸까.. 아마 이벤트에 대한 새로운 정보가 아닌(실시간으로 사용자의 이벤트에 기반한게 아닌), 과거의 행동의 재사용이라서 그러지 않을까 추측해본다.

 

그나저나 이벤트의 양이 많으면 replay에 대한 속도도 느릴 것 같은데, 다음 시간에 성능 개선을 한다고 한다..!

728x90
반응형
반응형

2022.01.18 - [개발/spring] - [axon] snapshot - clone coding

 

[axon] snapshot - clone coding

2022.01.12 - [개발/spring] - [axon] difficulties while clone coding [axon] difficulties while clone coding 2022.01.12 - [architecture/MSA] - [cqrs] axon framework [cqrs] axon framework 2022.01.11 -..

bangpurin.tistory.com

 

저번 글에 이어서 state stored aggregate 방식에 대해 알아본다.

 클론 코딩 참고 블로그는 다음와 같다: https://cla9.tistory.com/13?category=814447

 

9. Command 어플리케이션 구현 - 4

1. 서론 이번 포스팅은 데모 프로젝트 진행에 있어 필수적으로 구현해야하는 코드는 없습니다. 따라서 Skip해도 괜찮습니다. 이번 시간에는 상태를 저장하는 State-Stored Aggregate에 대해서 알아보겠

cla9.tistory.com

 

state store aggregate 으로 전환하고 실행, api 쏴보니 아래와 같은 jpa 에러가 난다.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.cqrs.command.aggregate.HolderAggregate.accounts, could not initialize proxy - no Session

fetch type을 eager로 바꾼다.

    @OneToMany(mappedBy = "holder", orphanRemoval = true, fetch = FetchType.EAGER)
    private List<AccountAggregate> accounts = new ArrayList<>();

참고: https://ankonichijyou.tistory.com/entry/JPA-OneToMany-%EC%98%A4%EB%A5%98

 

다시 하는데 아래와 같은 에러가 난다... 열심히 구글링했는데 별다른 힌트를 얻지 못해서.. axonserver 를 재시작했는데.. 세상에 잘 된다..

(설정을 변경해보려다 실패해서 얻어걸린 재시작 ㅎ) .. 내 2시간..ㅎ

2022-01-19 14:15:50.006  WARN 6587 --- [ault-executor-3] o.a.a.c.u.ResubscribableStreamObserver   : A problem occurred in the stream.

io.grpc.StatusRuntimeException: CANCELLED: HTTP/2 error code: CANCEL
Received Rst Stream
..

Hibernate: insert into account (balance, holder_id, account_id) values (?, ?, ?)
2022-01-19 14:15:50.010 ERROR 6587 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] threw exception

org.axonframework.axonserver.connector.command.AxonServerCommandDispatchException: CANCELLED: HTTP/2 error code: CANCEL
Received Rst Stream
...
2022-01-19 14:15:50.010 ERROR 6587 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is AxonServerCommandDispatchException{message=CANCELLED: HTTP/2 error code: CANCEL
Received Rst Stream, errorCode='AXONIQ-4003', server='6587@AL01590036.local'}] with root cause

org.axonframework.axonserver.connector.command.AxonServerCommandDispatchException: CANCELLED: HTTP/2 error code: CANCEL
Received Rst Stream
...
2022-01-19 14:35:34.782  WARN 6587 --- [ault-executor-7] o.a.a.c.u.ResubscribableStreamObserver   : A problem occurred in the stream.

io.grpc.StatusRuntimeException: UNAVAILABLE: HTTP/2 error code: NO_ERROR
Received Goaway
app_requested

 

hibernate sql log가 지나간다.

Hibernate: insert into holder (address, holder_name, tel, holder_id) values (?, ?, ?, ?)
---
Hibernate: select holderaggr_.holder_id, holderaggr_.address as address2_2_, holderaggr_.holder_name as holder_n3_2_, holderaggr_.tel as tel4_2_ from holder holderaggr_ where holderaggr_.holder_id=?
Hibernate: insert into account (balance, holder_id, account_id) values (?, ?, ?)
---
Hibernate: select accountagg0_.account_id as account_1_0_0_, accountagg0_.balance as balance2_0_0_, accountagg0_.holder_id as holder_i3_0_0_ from account accountagg0_ where accountagg0_.account_id=? for update
Hibernate: select holderaggr0_.holder_id as holder_i1_2_0_, holderaggr0_.address as address2_2_0_, holderaggr0_.holder_name as holder_n3_2_0_, holderaggr0_.tel as tel4_2_0_, accounts1_.holder_id as holder_i3_0_1_, accounts1_.account_id as account_1_0_1_, accounts1_.account_id as account_1_0_2_, accounts1_.balance as balance2_0_2_, accounts1_.holder_id as holder_i3_0_2_ from holder holderaggr0_ left outer join account accounts1_ on holderaggr0_.holder_id=accounts1_.holder_id where holderaggr0_.holder_id=?

Hibernate: update account set balance=?, holder_id=? where account_id=?
---

db를 봐도 잘 들어가 있다.

 

event sourced aggregate 방식과 다르게 매 스탭의 결과를 디비에 저장하기 때문에 매번 작업을 새롭게 하지 않는다는 장점이 있으나, 디비 연결에 대한 네트워크 이슈(속도), 디비가 날아가면 히스토리를 알기 어려움 등의 단점도 알아둬야겠다.

디비에 저장하면서 이벤트 발생 히스토리도 별도로 저장하면 좋지 않을까?

 

추가)

다시 소스를 보니 @CommandHandler 어노테이션만 있으면 별도의 repository나 save 등의 액션이 없이도 디비에 수정사항이 생기고 변화하는 것을 알 수 있다.

원문을 보면 아래와 같은 문구를 확인할 수 있다. 없으면 자동 생성해서 해주는 듯 하다.

Axon will automatically register all the @CommandHandler annotated methods with the command bus
and set up a repository if none is present.

 

또한 각 aggregate에 repository를 명시할 수도 있다.

@Aggregate(repository = "repositoryForGiftCard")


원문: https://docs.axoniq.io/reference-guide/v/4.0/configuring-infrastructure-components/command-processing/command-model-configuration

 

Command Model Configuration - Axon Reference Guide

To fully customize the repository used, you can define one in the application context. For Axon Framework to use this repository for the intended aggregate, define the bean name of the repository in the repository attribute on @Aggregate Annotation. Altern

docs.axoniq.io

 

 

728x90
반응형
반응형

이전글: 2022.01.12 - [개발/spring] - [axon] command/query project 생성 - clone coding

 

[axon] command/query project 생성 - clone coding

2022.01.12 - [architecture/MSA] - [cqrs] axon framework [cqrs] axon framework 2022.01.11 - [architecture/MSA] - [arch] what is cqrs - 조회와 비조회의 엄연한 분리 [arch] what is cqrs - 조회와 비조회..

bangpurin.tistory.com

이전 글에 이어서 axon framework의 command에 대한 클론 코딩을 이어가던 중 실습 내용에 대한 고찰을 해본다.

참고)

  필수 어노테이션
command @TargetAggregateIdentifier
aggregate @Aggregate
@AggregateIdentifier

 

클론 코딩 참고 블로그는 아래와 같다: https://cla9.tistory.com/12?category=814447 

 

8. Command 어플리케이션 구현 - 3

1. 서론 지난 포스팅에서 Command Application에 대한 전반적인 구현을 마무리했습니다. 하지만 해당 프로그램은 근본적인 문제점을 안고 있습니다. 이번 포스팅에서는 발생되는 문제점과 이를 해결

cla9.tistory.com

따라해본다.

POST http://localhost:8080/holder
POST http://localhost:8080/account
POST http://localhost:8080/deposit
POST http://localhost:8080/withdrawal
POST http://localhost:8080/withdrawal
POST http://localhost:8080/withdrawal

위 글에도 써있지만 아래와 같은 순서로 잔고를 인출했을 때의 로그는 아래와 같다(withdrawal을 10원씩 4번째 호출했을 때이다).

다시 말하지만 api 한번 호출 시의 로그이다.

[nio-8080-exec-3] o.a.commandhandling.SimpleCommandBus     : Handling command [com.cqrs.command.commands.WithdrawMoneyCommand]
[nio-8080-exec-3] o.a.m.unitofwork.AbstractUnitOfWork      : Starting Unit Of Work
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.AbstractUnitOfWork$$Lambda$1197/0x00000008008b5040 for phase ROLLBACK
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase STARTED
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.UnitOfWork$$Lambda$1201/0x00000008008b4040 for phase COMMIT
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.UnitOfWork$$Lambda$1202/0x00000008008b4440 for phase ROLLBACK
[nio-8080-exec-3] o.a.a.c.event.axon.AxonServerEventStore  : Reading events for aggregate id 2b158c88-b810-4643-9db4-4096edea5621
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : >> applying AccountCreationEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621)
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : applying DepositMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=300)
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : balance 300
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : applying WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10)
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : balance 290
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : applying WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10)
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : balance 280
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : applying WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10)
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : balance 270
[nio-8080-exec-3] org.axonframework.messaging.Scope        : Clearing out ThreadLocal current Scope, as no Scopes are present
[ault-executor-3] o.a.a.c.e.AxonServerEventStoreClient     : Done request for 2b158c88-b810-4643-9db4-4096edea5621: 22ms, 5 events
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.modelling.command.LockingRepository$$Lambda$1300/0x0000000800a0d440 for phase CLEANUP
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.modelling.command.AbstractRepository$$Lambda$1301/0x0000000800a0d840 for phase ROLLBACK
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.modelling.command.AbstractRepository$$Lambda$1302/0x0000000800a0dc40 for phase PREPARE_COMMIT
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : handling WithdrawMoneyCommand(accountID=2b158c88-b810-4643-9db4-4096edea5621, holderID=76147ef5-280f-439e-b017-bcd04028f176, amount=10)
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : applying WithdrawMoneyEvent(holderID=76147ef5-280f-439e-b017-bcd04028f176, accountID=2b158c88-b810-4643-9db4-4096edea5621, amount=10)
[nio-8080-exec-3] c.c.command.aggregate.AccountAggregate   : balance 260
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1222/0x00000008008f3440 for phase AFTER_COMMIT
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1223/0x00000008008f3840 for phase ROLLBACK
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1225/0x00000008008f4040 for phase PREPARE_COMMIT
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1226/0x00000008008f4440 for phase COMMIT
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1227/0x00000008008f4840 for phase AFTER_COMMIT
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1228/0x00000008008f4c40 for phase CLEANUP
[nio-8080-exec-3] org.axonframework.messaging.Scope        : Clearing out ThreadLocal current Scope, as no Scopes are present
[nio-8080-exec-3] o.a.m.unitofwork.AbstractUnitOfWork      : Committing Unit Of Work
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase PREPARE_COMMIT
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.axonserver.connector.event.axon.AxonServerEventStore$AxonIQEventStorageEngine$$Lambda$1245/0x000000080091f040 for phase ROLLBACK
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.axonserver.connector.event.axon.AxonServerEventStore$AxonIQEventStorageEngine$$Lambda$1246/0x000000080091f440 for phase COMMIT
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase COMMIT
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase AFTER_COMMIT
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase CLEANUP
[nio-8080-exec-3] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase CLOSED

관련 dto..

WithdrawMoneyCommand.java
    @TargetAggregateIdentifier
    private String accountID;

/withdraw api의 aggregateId 인 acoountId 에 대한 모든 이벤트를 다시 불러오는 듯 하다. 즉 위 로그처럼 acoountId에 대한 모든 이벤트를 롤백하고(마치 계좌가 없던 것 처럼) 다시 첨부터 하는 것을 반복한다. 즉 여기서는 계좌 생성(/account), 입금(/deposit), 출금 n번(/withdraw)이다.

 

event sourced aggregate

신박하다.. 데이터의 정합성은 보장될 것 같은데 효율성은 떨어질 것 같다.

 

snapshot

그래서 클론 코딩에서 알려준대로 스냅샷을 적용하고 새로운 마음으로 계정생성1, 입금2, 인출3, 인출4, 인출5 를 해본다.

아래는 인출5 api실행 시 로그다.

[nio-8080-exec-5] o.a.commandhandling.SimpleCommandBus     : Handling command [com.cqrs.command.commands.WithdrawMoneyCommand]
[nio-8080-exec-5] o.a.m.unitofwork.AbstractUnitOfWork      : Starting Unit Of Work
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.AbstractUnitOfWork$$Lambda$1192/0x000000080087ac40 for phase ROLLBACK
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase STARTED
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.UnitOfWork$$Lambda$1196/0x0000000800879c40 for phase COMMIT
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.UnitOfWork$$Lambda$1197/0x0000000800879040 for phase ROLLBACK
[nio-8080-exec-5] o.a.a.c.event.axon.AxonServerEventStore  : Reading events for aggregate id f00088d7-c81a-432c-b010-28a88012f0eb
[ault-executor-4] o.a.a.c.e.AxonServerEventStoreClient     : Done request for f00088d7-c81a-432c-b010-28a88012f0eb: 12ms, 4 events
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : >> applying AccountCreationEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb)
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : applying DepositMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=400)
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : balance 400
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : applying WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10)
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : balance 390
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : applying WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10)
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : balance 380
[nio-8080-exec-5] org.axonframework.messaging.Scope        : Clearing out ThreadLocal current Scope, as no Scopes are present
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.modelling.command.LockingRepository$$Lambda$1246/0x00000008009c2c40 for phase CLEANUP
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.modelling.command.AbstractRepository$$Lambda$1247/0x00000008009c3040 for phase ROLLBACK
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.modelling.command.AbstractRepository$$Lambda$1249/0x00000008009c3840 for phase PREPARE_COMMIT
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : handling WithdrawMoneyCommand(accountID=f00088d7-c81a-432c-b010-28a88012f0eb, holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, amount=10)
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : applying WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10)
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : balance 370
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1260/0x00000008009c6440 for phase AFTER_COMMIT
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1261/0x00000008009c6840 for phase ROLLBACK
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1263/0x00000008009c7040 for phase PREPARE_COMMIT
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1264/0x00000008009c7440 for phase COMMIT
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1265/0x00000008009c7840 for phase AFTER_COMMIT
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1266/0x00000008009c7c40 for phase CLEANUP
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventsourcing.EventCountSnapshotTriggerDefinition$EventCountSnapshotTrigger$$Lambda$1245/0x00000008009c2840 for phase PREPARE_COMMIT
[nio-8080-exec-5] org.axonframework.messaging.Scope        : Clearing out ThreadLocal current Scope, as no Scopes are present
[nio-8080-exec-5] o.a.m.unitofwork.AbstractUnitOfWork      : Committing Unit Of Work
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase PREPARE_COMMIT
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.axonserver.connector.event.axon.AxonServerEventStore$AxonIQEventStorageEngine$$Lambda$1276/0x00000008009ce840 for phase ROLLBACK
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.axonserver.connector.event.axon.AxonServerEventStore$AxonIQEventStorageEngine$$Lambda$1277/0x00000008009cec40 for phase COMMIT
[nio-8080-exec-5] o.a.a.c.event.axon.AxonServerEventStore  : Reading events for aggregate id f00088d7-c81a-432c-b010-28a88012f0eb
[ault-executor-4] o.a.a.c.e.AxonServerEventStoreClient     : Done request for f00088d7-c81a-432c-b010-28a88012f0eb: 10ms, 4 events
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : >> applying AccountCreationEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb)
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : applying DepositMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=400)
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : balance 400
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : applying WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10)
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : balance 390
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : applying WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10)
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : balance 380
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : applying WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10)
[nio-8080-exec-5] c.c.command.aggregate.AccountAggregate   : balance 370
[nio-8080-exec-5] org.axonframework.messaging.Scope        : Clearing out ThreadLocal current Scope, as no Scopes are present
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase COMMIT
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase AFTER_COMMIT
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase CLEANUP
[nio-8080-exec-5] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase CLOSED
[ault-executor-4] o.a.a.c.event.axon.AxonServerEventStore  : Snapshot created

 왜 400 -> 370 까지 로그가 두 번 찍히는지는 모르겠지만 마지막에 snapshot created.

이 후 인출6 시 로그. 확실히 400 -> 370 까지 다시 하지 않고 바로 360으로 간다.

[nio-8080-exec-7] o.a.commandhandling.SimpleCommandBus     : Handling command [com.cqrs.command.commands.WithdrawMoneyCommand]
[nio-8080-exec-7] o.a.m.unitofwork.AbstractUnitOfWork      : Starting Unit Of Work
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.AbstractUnitOfWork$$Lambda$1192/0x000000080087ac40 for phase ROLLBACK
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase STARTED
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.UnitOfWork$$Lambda$1196/0x0000000800879c40 for phase COMMIT
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.messaging.unitofwork.UnitOfWork$$Lambda$1197/0x0000000800879040 for phase ROLLBACK
[nio-8080-exec-7] o.a.a.c.event.axon.AxonServerEventStore  : Reading events for aggregate id f00088d7-c81a-432c-b010-28a88012f0eb
[ault-executor-5] o.a.a.c.e.AxonServerEventStoreClient     : Done request for f00088d7-c81a-432c-b010-28a88012f0eb: 15ms, 1 events
[nio-8080-exec-7] org.axonframework.messaging.Scope        : Clearing out ThreadLocal current Scope, as no Scopes are present
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.modelling.command.LockingRepository$$Lambda$1246/0x00000008009c2c40 for phase CLEANUP
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.modelling.command.AbstractRepository$$Lambda$1247/0x00000008009c3040 for phase ROLLBACK
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.modelling.command.AbstractRepository$$Lambda$1249/0x00000008009c3840 for phase PREPARE_COMMIT
[nio-8080-exec-7] c.c.command.aggregate.AccountAggregate   : handling WithdrawMoneyCommand(accountID=f00088d7-c81a-432c-b010-28a88012f0eb, holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, amount=10)
[nio-8080-exec-7] c.c.command.aggregate.AccountAggregate   : applying WithdrawMoneyEvent(holderID=b232a9e8-d280-48bc-9d17-df03c9fe4071, accountID=f00088d7-c81a-432c-b010-28a88012f0eb, amount=10)
[nio-8080-exec-7] c.c.command.aggregate.AccountAggregate   : balance 360
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1260/0x00000008009c6440 for phase AFTER_COMMIT
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1261/0x00000008009c6840 for phase ROLLBACK
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1263/0x00000008009c7040 for phase PREPARE_COMMIT
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1264/0x00000008009c7440 for phase COMMIT
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1265/0x00000008009c7840 for phase AFTER_COMMIT
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.eventhandling.AbstractEventBus$$Lambda$1266/0x00000008009c7c40 for phase CLEANUP
[nio-8080-exec-7] org.axonframework.messaging.Scope        : Clearing out ThreadLocal current Scope, as no Scopes are present
[nio-8080-exec-7] o.a.m.unitofwork.AbstractUnitOfWork      : Committing Unit Of Work
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase PREPARE_COMMIT
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.axonserver.connector.event.axon.AxonServerEventStore$AxonIQEventStorageEngine$$Lambda$1276/0x00000008009ce840 for phase ROLLBACK
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Adding handler org.axonframework.axonserver.connector.event.axon.AxonServerEventStore$AxonIQEventStorageEngine$$Lambda$1277/0x00000008009cec40 for phase COMMIT
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase COMMIT
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase AFTER_COMMIT
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase CLEANUP
[nio-8080-exec-7] o.a.m.u.MessageProcessingContext         : Notifying handlers for phase CLOSED

그 이후로도 입금/출금 계속 했더니 위처럼 반복되다가 5번째마다 스냅샷이 잘 찍히는 것을 알 수 있다.


실패기록:

api를 몇 번 랜덤하게 쐈더니 아래와 같은 에러가 났다.

AxonServerException OUT_OF_RANGE: [AXONIQ-2000] Invalid sequence number while storing snapshot.

..왜 나에게만 이런 시련이

스택오버플로우에서 알려준대로 application.yml에 아래를 추가하고 재시작하니 해결되었다.. (하지만 그거시 아니었다..ㅎ)

axon:
    eventhandling:
        processors:
            name:
                mode: tracking

무슨 의미인지 찾아보던 중, 원문에 default가 tracking이라는데 저걸 또 명시하는게 무슨 의미인가 싶은데... 또 에러가 해결은 되고... ㅎ

Event Processors come in roughly two forms: Subscribing and Tracking. The Subscribing Event Processors subscribe themselves to a source of Events and are invoked by the thread managed by the publishing mechanism. Tracking Event Processors, on the other hand, pull their messages from a source using a thread that it manages itself.
...
By default, Axon will use Tracking Event Processors.

event processor 설명: https://cla9.tistory.com/15?category=814447 

 

11. Query 어플리케이션 구현(Event) - 1

1. 서론 이번 포스팅부터 Query App 구현을 다루겠습니다. Query App은 Event를 수신받아 Read Model에 반영하는 Projection 작업과 Read Model을 읽는 Query 2가지로 기능이 나뉩니다. 기능별로 실제 구현 코드..

cla9.tistory.com

 

아니, 해결된 줄 알고 또 몇 번 해보니 아래의 에러가 나온다. 구글링해도 뭐가 안 나온다.. 돌겠네..ㅋㅋㅋㅋ 

ConcurrencyException: OUT_OF_RANGE: [AXONIQ-2000] Invalid sequence number while storing snapshot.

 

내가 같은 holderId로 계좌 생성을 두번했는데, 서로 다른 계좌가 생성되긴 했지만,, 뭔가 그 후로 sequence 가 꼬인 듯.. 싶다.

무튼 저거 버리고 다시 처음부터 하니 그 후론 재연도 안된다 ㅎㅅㅎ;

 

궁금한게, 분명 axon server 안에서 저 이벤트/스냅샷을 저장하고 있다가 복기하는 것 같은데.. 어디에 있는지 모르겠다.

그걸 확인해서 꼬인 부분을 해결해보려고 했는데, 프로퍼티에 설정한 디비 테이블을 뒤져도 다 empty라서.. 어디에 저장하는지 궁금하다.

+ 찾아보니,

현재 구현 버전은 event-sourced-aggregate 이라하여 axon server의 event store에 저장하는 구현 방식이다. 즉 설정파일(axonserver.properties)에 지정한 경로에 파일로 각 이벤트를 저장하는 것이다. 

아래는 현재 내 axonserver의 설정과 데이터 값의 예시이다.

[root@localhost axonserver]# cat axonserver.properties 

logging.file=/var/log/axonserver/axonserver.log
logging.file.max-history=10
logging.file.max-size=10MB
axoniq.axonserver.event.storage=./events
axoniq.axonserver.snapshot.storage=./events
axoniq.axonserver.controldb-path=./data
-------------------------------------------------
[root@localhost default]# pwd
/var/lib/axonserver/events/default
[root@localhost default]# ll
-rw-r--r--. 1 axonserver axonserver 268435456  1월 18 17:11 00000000000000000000.events
-rw-r--r--. 1 axonserver axonserver 268435456  1월 18 17:11 00000000000000000000.snapshots

/// event 파일 예시

$a1fe8538-2955-4311-b371-b55811b5d1a1^R$f00088d7-c81a-432c-b010-28a88012f0eb^X^N"^PAccountAggregate(å<84><9d>áæ/2÷^A
"com.cqrs.events.WithdrawMoneyEvent^ZÐ^A<com.cqrs.events.WithdrawMoneyEvent><holderID>b232a9e8-d280-48bc-9d17-df03c9fe4071</holderID><accountID>f00088d7-c81a-432c-b010-28a88012f0eb</accountID><amount>10</amount></com.cqrs.events.WithdrawMoneyEvent>rW<96>^]^@^@^Ae^B^@^A^@^@^Aa

/// snapshot 파일 예시

$67179188-1d02-4a6a-ade3-49cd5586b88c^R$f00088d7-c81a-432c-b010-28a88012f0eb^X^H"^PAccountAggregate(<93>ñ<87>àæ/2<95>^B
+com.cqrs.command.aggregate.AccountAggregate^Zå^A<com.cqrs.command.aggregate.AccountAggregate><accountID>f00088d7-c81a-432c-b010-28a88012f0eb</accountID><holderID>b232a9e8-d280-48bc-9d17-df03c9fe4071</holderID><balance>740</balance></com.cqrs.command.aggregate.AccountAggregate>±C>Ò^@^@^A<84>^B^@^A^@^@^A<80>

 

위의 나의 궁금증을 해결하기 위해서는 다음 글에서 설명하는 (디비에 저장하는) state-stored-aggregate을 공부하면 될 듯 하다.


참고자료

설명

https://docs.axoniq.io/reference-guide/v/4.0/configuring-infrastructure-components/event-processing/event-processors

 

Event Processors - Axon Reference Guide

By default, exceptions that are raised by Event Handlers are logged, and processing continues with the next events. Exceptions that are thrown when a processor is trying to commit a transaction, update a token, or in any other other part of the process, th

docs.axoniq.io

boot 설정 값

https://docs.axoniq.io/reference-guide/v/3.3/part-iii-infrastructure-components/spring-boot-autoconfiguration

 

Spring Boot AutoConfiguration - Axon Reference Guide

To fully customize the repository used, you can define one in the Application Context. For Axon Framework to use this repository for the intended Aggregate, define the bean name of the repository in the repository attribute on @Aggregate Annotation. Altern

docs.axoniq.io

 

728x90
반응형

+ Recent posts