1. 서론

 

이번 포스팅부터 생성된 Read Model에 대하여 Query하는 방법에 대하여 소개하겠습니다. AxonServer를 통해서 수많은 MicroService App들이 연결되어 있을 수 있습니다. 이러한 환경에서 Query 요청을 했을때, 단순 1:1 요청 응답을 요구할 수 도 있고 때로는 Query 요청에 따라 2개 이상의 다른 App에서 데이터를 수신받는 경우도 있습니다. 따라서 각기 다른 경우에 따라 처리해야하는 방법이 다릅니다.

 

Axon Framework는 총 3가지 타입의 Query 기능을 제공합니다. 이번 포스팅에서는 Axon 에서 제공하는 Query 종류와 동작 원리를 알아보겠습니다.


2. Axon Server 라우팅 기능(Query)

 

Command 명령을 요청할 때는 CommandBus를 이용하였고, Event 발생시에는 EventBus를 이용하였습니다. 마찬가지로 Query는 QueryGateway를 통해 요청을 전달하며, 전달된 Query는 QueryBus를 통해서 해당 Query를 처리하는 Handler로 연결됩니다. 이때 QueryHandler가 속한 App에 Query를 전달하는 역할을 Axon Server가 수행합니다. Query 관련 Axon Server의 라우팅 기능 동작 흐름을 살펴보겠습니다.

 

 

1. Point to Point Query

 

 

Command Handler와 마찬가지로 Application 기동시 AxonServer와 연결을 시도합니다. 연결이 완료되면, 해당 App은 자신이 처리가능한 Query Handler 정보를 Server에 등록합니다. Point to Point Query는 해당 Query를 처리하는 Handler가 단 하나의 Application에만 존재할 경우 해당 처리할 수 있는 App으로 Query를 전달하여 결과를 전달합니다.


2. Scatter & Gatter Query

 

 

 

두번째는 Scatter-Gather Query입니다. 이는 동일한 Query를 처리하는 Handler가 여러 App에 등록되어있을 때, 이를 처리하는 방법입니다. Application 기동시 Query Handler 정보를 Axon Server에 등록하면, Application 정보가 라우팅 테이블에 해당 App 정보를 기록합니다. 이때는 Client 측에서 각기 다른 App에서 수신되는 결과를 수집하여 처리 방법(한쪽 결과만 수집, 둘다 수집 등)을 정해야합니다. Axon Server는 Query 요청이 들어오면 등록된 Application에게 Query를 전달하며, 수신받은 App에서는 결과를 취합하여 전송합니다. 이후 Client 측에서 결과를 수신받아 데이터 결과를 종합합니다.


3. Subscription Query

 

 

Point to Point Query를 요청하였을 때, 만약 Query를 수행하는 Read Model에 대한 변경이 발생한다면, 화면에 출력되는 결과와 Read Model 사이 데이터 정합성 불일치 문제가 발생합니다. 따라서 이를 해결하기 위해서는 주기적으로 Query를 재요청하는 방법이 있습니다. 하지만 데이터 변경이 발생하지 않아도 계속 Query를 요청해야하는 문제점이 있으므로 효율적이지 못합니다.

 

 

 

Subscription Query는 Client측에서 Query를 요청할 때, Query 결과를 전달받고 Connection을 끊는 것이 아니라 계속 지속합니다. 이후 Query Handler가 위치한 App의 Read Model 변경이 발생할 경우 변경분에 대한 데이터를 전달받아 이를 최신화합니다.


3. Query Handler 동작 과정

 

 

AxonFramework 관련 Bean 생성시 사용자가 지정한 QueryBus가 없으면, Default로 AxonServerQueryBus가 생성됩니다. 이때 내부적으로 QueryProcessor가 만들어지고, 해당 생성자 안에서 ExecuterService를 통해 요청시 최대 10개의 Thread를 Default로 생성하도록 Handler에 등록합니다. 

 

ExecuterService에 의해서 만들어지는 Thread는 QueryProcessingTask이며, AxonServer로부터 Query를 전달받으면 해당 클래스의 run 메소드를 통해 QueryHandler 작업이 이어집니다.

 

 

 

Application 구동 이후, Query 요청이 발생되면, 내부적으로는 위와 같은 흐름을 거쳐 메시지가 전달됩니다.

 

  1. QueryGateway로 Query를 전달합니다. 이때 전달하는 Query가 Scatter-Gather, Subsciprtion, Point to Point 중 하나임을 메소드를 통해 AxonServer에게 전달합니다.
  2. 사용자가 전달한 Query를 GenericQueryMessage로 변환한다음 QueryBus로 전달합니다. Default QueryBus는 AxonServerQueryBus이므로 AxonServer에 전달됩니다.
  3. 전달된 Query는 QueryHandler가 존재하는 App으로 라우팅됩니다. 이때 AxonServer로부터 gRPC를 통해 onMessage 메소드가 호출되면, AxonServerQueryBus 내부에 할당된 Handler들의 onNext 메소드를 호출합니다.
  4. Bean 등록 당시 Handler 호출시 ExecutorService로부터 QueryProcessingTask 생성을 요청하였습니다. 따라서 Handler 호출과정에서 Thread 생성을 요청합니다.
  5. QueryProcessingTask 내부에 있는 run 메소드가 수행되면서 QueryProcessor에게 Query 수행을 위임합니다.
  6. QueryProcessor 내부 로직 수행중 Query 수행을 SimpleQueryBus에게 위임합니다.
  7. 내부적으로 UnitOfWork 과정을 거치면서, Reflection을 통해 QueryHandler 메소드를 찾아 수행후 결과를 돌려 받습니다.
  8. 최종 수행된 결과를 QueryProviderOutbound에게 전달합니다.
  9. AxonServer에게 결과를 전달합니다.
  10. Query를 요청한 Client에게 결과를 전달합니다.

4. 마치며

 

Query App 구현을 위해 기본적으로 알아야하는 내부 과정에 대해서 살펴봤습니다. 다음 포스팅에서는 코드 구현을 진행하겠습니다.

+ Recent posts