반응형

이전 글: 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
반응형

+ Recent posts