1. 서론

 

CQRS 구성을 위해 일반적으로 Command, Query 두가지 Application을 구성합니다. 이때 Application 사이 매개체 역할을 Event가 담당합니다. 따라서 Event 정보를 알기 위해서는 Event 정보가 두 App에 모두 포함되어야 합니다.

 

 

일반적인 프로젝트 구조라면 동일한 소스 코드가 두 App에 모두 존재해야되므로 Event의 변경사항이 있을 때 양쪽 Application의 구조를 바꿔야합니다. 더군다나 Axon에서는 Event 클래스의 패키지 구조가 동일해야되는 제약사항이 존재합니다. 따라서 이러한 문제를 해결하기 위한 다양한 방법중 Gradle을 활용해서 MultiProject 구성을 하고자 합니다. 

 

 

즉 Event 클래스만 모은 공통 모듈을 작성하고, Command, Query에서 이를 참조하도록 구성합니다. 이렇게 별도 모듈로 분리함으로서 변경 사항 발생시 공통 모듈에만 변경을 가하면 양쪽 App에 적용되므로 소스 관리가 용이해집니다.

 

자세한 내용은 Gradle 공식 문서를 참조하시기 바라며, 이번 포스팅에서는 기본적인 Gradle Multi Project 구성 방법에 대해서 알아보겠습니다.

 

참고 블로그

와이케이 마구잡이님 블로그

jojoldu님 블로그


2. Gradle 프로젝트 생성

 

1. IntellJ에서 Create New Project 를 클릭합니다.

 


2. Spring 프로젝트를 만들기 위해서 Spring Initializr를 선택합니다. 이후 Java SDK 버전 선택한 다음 Next 버튼을 눌러 다음 단계로 진행합니다.

 


3. Gradle 프로젝트 생성을 위해 Type을 Gradle Project로 설정합니다. 이후 Group과 Artifact를 본인 프로젝트 구성에 맞게 기입하니다. 마지막으로 Java version을 PC에 설치된 Java 버전과 동일하게 설정 후 Next 버튼을 선택합니다.

 


4. 의존성은 나중에 별도 추가할 예정이므로 Next 버튼을 눌러 다음 단계로 이동합니다.

 


5. Project 이름 설정 후, Finish 버튼을 선택합니다.

 


6. Gradle 설정 화면에서 특별하게 변경해야할 사항이 없다면 기본 설정 상태에서 OK 버튼을 선택합니다.

 


7. 의존성이 정상적으로 추가되면 아래 이미지 하단과같이 sync가 정상적으로 이루어짐을 확인할 수 있습니다. 지금 생성한 프로젝트는 root 프로젝트이므로 src 폴더 전체를 선택 후 삭제합니다.

 

 


3. Multi Module 구성하기

 

1. 프로젝트내 모듈은 3가지(Command, Query, Common)입니다. 따라서 이를 구성하기 위해서 root 프로젝트 내 settings.gradle 파일을 연 후에 아래 이미지와 같이 sub module명을 기입합니다.

 


2. 이제부터 프로젝트 구성을 위해서 구조 변경이 필요합니다. 먼저 root 프로젝트에 있는 build.gradle 파일을 엽니다. 이후 AS-IS로 되어있는 구조를 TO-BE 형태로 변경합니다.

 

AS-IS

plugins {
    id 'org.springframework.boot' version '2.2.2.RELEASE'
    id 'io.spring.dependency-management' version '1.0.8.RELEASE'
    id 'java'
}

group = 'com.cqrs'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

test {
    useJUnitPlatform()
}

 

TO-BE

buildscript {
    ext {
        springBootVersion = '2.2.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

allprojects {
    group = 'com.cqrs'
    version = '0.0.1-SNAPSHOT'
}

subprojects {
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'
    apply plugin: 'java'

    sourceCompatibility = '11'

    repositories {
        mavenCentral()
    }

    dependencies {
        testImplementation('org.springframework.boot:spring-boot-starter-test') {
            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
        }
    }

    task initSourceFolders {
        sourceSets*.java.srcDirs*.each {
            if (!it.exists()) {
                it.mkdirs()
            }
        }
        sourceSets*.resources.srcDirs*.each {
            if (!it.exists()) {
                it.mkdirs()
            }
        }
    }
}

project(':command') {
    dependencies {
        compile project(':common')
    }
}

project(':query') {
    dependencies {
        compile project(':common')
    }
}

 

변경한 build.script 내용을 설명하면 다음과 같습니다.

 

buildscript 블록 : 나머지 스크립트를 빌드하는 과정에서 필요한 외부 라이브러리를 classpath에 추가하는 기능을 담당합니다. subprojects 내에서 플러그인 적용(apply plugin)이 가능한 이유 또한 buildscript를 통해 라이브러리를 classpath에 추가시켰기 때문입니다.

 

allprojects 블록 :  root 프로젝트(demo)와 하위 프로젝트(command, query, common)에 모두 적용되는 빌드 스크립트 기준을 작성합니다.

 

subprojects 블록 : 하위 프로젝트(command, query, common)에만 적용되는 빌드 스크립트 기준을 작성합니다.

  • sourceCompatibility : java 버전을 명시합니다.
  • repositories : 저장소 설정을 담당합니다. 
  • initSourceFolders task : sub module별로 기초 디렉터리가 존재하지 않으면, 자동 생성해주도록 설정합니다.

 

projects 블록 : Command, Query App은 빌드시에 공통 모듈(Common)이 포함되어야 함으로 빌드시에 추가하도록 설정합니다.

(※ : 가 들어간 이유는 Root 프로젝트 기준으로 각 모듈은 한단계 아래 계층에 존재하기 때문에 이를 구별하기 위함입니다.)


3. intellij 우측 gradle 탭을 엽니다. 이후 root 프로젝트 > Tasks > build > build를 더블클릭하여 build를 시도합니다. build 수행하면 root 프로젝트에 src 폴더를 지웠기 때문에 build 실패가 발생하지만, 좌측탭에 command, common, query 폴더가 생긴 것을 확인할 수 있습니다.

 


4. sub module 폴더 내에 build.gradle 파일을 생성합니다.


5. Command, Query App에서는 Spring Web MVC를 사용하기 때문에 build.gradle에 의존성을 추가합니다.

 


6. Command 모듈에서 패키지 생성을 위해 src > main > java 디렉토리 선택 후 [Alt + Insert] 키를 누릅니다. 이후 package 탭을 선택합니다.

 


7. 임의의 package 명을 입력한 후 OK 버튼을 누릅니다.

 


8. 생성된 package 하위에 App 실행을 위한 main 클래스를 작성합니다.

 


9. App을 구동하여 정상 작동하는지 확인합니다.

 


10. 5~9번 작업을 query 모듈에도 반복합니다.

 

 

위와 같이 정상적으로 수행된다면 Multi Project 기초 구성은 끝났습니다.


4. 마치며

이번 포스팅에서는 각 모듈별 기초적인 의존성 추가 및 모듈 구성을 했습니다. 다음 포스팅에서는 데모 프로젝트 진행을 위해서 각 모듈별 필요한 의존성 추가 및 코드 구현을 본격적으로 하겠습니다.

 

1. 개요

 

지금부터 EventSourcing과 CQRS가 적용된 프로젝트를 구현하면서 AxonFramework 사용법을 배워봅니다.

이에 앞서 앞으로 진행할 프로젝트에 대한 설계를 통해 구조를 잡아보겠습니다.

 

 

프로그램 요구사항 

 

  • 계정 생성시 소유주, 전화번호, 주소를 입력한다.
  • 계정 생성이 완료되면, 계정 ID가 발급된다.
  • 계좌 생성시, 계정 ID를 입력한다.
  • 계좌 생성이 완료되면 고유한 계좌 번호를 반환한다.
  • 계좌번호를 통해서 입금 가능하다.
  • 계좌번호를 통해서 출금 가능하다.
  • 인출과정에서 잔액이 부족한경우 "잔액이 부족합니다." 메시지를 출력한다.
  • 소유자가 보유한 전체 계좌 개수 및 잔액 조회가 가능하다.

2. 프로그램 설계

 

앞으로 구현할 프로그램은 간단한 은행 입출금 관련 데모입니다. 짧은 요구사항이지만 도메인이 복잡해진다면 계좌 관리(Account)거래(Transacation)는 별도 Bounded Context로 분리할 수 있습니다. 하지만 데모 프로젝트에서는 편의상 두 도메인을 하나의 Bounded Context에 구현하겠습니다.

 


CQRS 

 

AxonFramework는 기본적으로 CQRS 아키텍처를 따르므로, 데모도 마찬가지로 해당 구조를 기반으로 설계했습니다. 즉 Command 와 Query App을 분리하고 각 App별로 DB를 사용하도록 구성했습니다.

 

편의상 두 App의 DB는 Postgresql로 통일하였으며, 각기 다른 스키마 사용을 통해 두 DB를 논리적으로 분리했습니다. Command DB는 AxonFramework에서 내부적으로 사용하는 saga, token, association_value 데이터를 관리합니다.

(※ Read 모델은 MongoDB로 구성하는 등의 Polyglot 구조 변경 가능합니다.)

 

또한 이미 앞선 장을 통해서 EventStore 및 메시지 라우팅을 담당하는 AxonServer를 사용하고 있으므로 이는 고려하지 않겠습니다.

 


다이어그램

 

데모에서 사용되는 구조는 다음과 같습니다. Command 모델은 소유주(holder)와 계좌(accont)로 나뉘어 Aggregation을 만들었습니다. 반면 Query 모델은 소유주의 전체 계좌의 총액을 보여주도록 Materialized View 를 ERD로 표현했습니다.

 

 

Command 

 

Aggregate

 

Query

 

Materialized View
summary


Command, Event 도출

 

이벤트 주도 개발 방법에서 가장 중요한 것은 이벤트 설계입니다. 이때 Event Storming 전략을 사용하여 Command, Event 등을 도출합니다.

(※ Event Storming 단계에서 포스트잇을 사용하는데, 비슷한 느낌을 주기 위해서 이미지를 사용했습니다.)

 

 

계정 생성

계좌 생성

입금

출금


서비스 EndPoint 설계

 

다음은 Controller 매핑되는 API EndPoint를 설계하겠습니다.

참고로 Command App은 8080 , Query App은 9090 Port를 사용했습니다.

 

계정 생성

POST : localhost:8080/holder
{  
    "holderName" : 소유주,
    "tel" : 전화번호,
    "address" : 주소
}

계좌 생성

POST : localhost:8080/account
{
    "holderID" : 계정 ID
}

 

입금

POST : localhost:8080/deposit
{
    "accountID" : 계좌번호,
    "holderID" : 계정 ID,
    "amount":입금액
}

 

출금

POST : localhost:8080/withdrawal
{
    "accountID" : 계좌번호,
    "holderID" : 계정 ID,
    "amount":출금액
}

 

계좌 정보 조회

GET : localhost:9090/account/info/{accountID}

3. 테스트 시나리오

 

마지막으로 실제 App 수행시 진행할 테스트 시나리오를 다음과 같이 작성했습니다.

 

  1. 고객 Kevin이 계정을 생성한다. (소유주 : Kevin, 전화번호 : 02-1234-5678, 주소 : 서울시)
  2. Kevin이 계좌를 개설한다.(소유주 ID : 1번 과정을 통해 생성된 UUID)
  3. Kevin계좌에 1000원을 입금한다(계좌 ID : 2번 과정을 통해 생성된 UUID, 입금액 : 1000)
  4. Kevin 계좌에서 100원을 인출한다(계좌 ID : 2번 과정을 통해 생성된 UUID, 출금액 : 100)
  5. Kevin 계좌에서 200원을 인출한다(계좌 ID : 2번 과정을 통해 생성된 UUID, 출금액 : 200)
  6. Kevin 전체 계좌 잔액 내역을 조회한다.
  7. Kevin 계좌에서 800원을 인출한다(계좌 ID : 2번 과정을 통해 생성된 UUID, 출금액 : 800)
  8. "잔액이 부족합니다." 메시지를 확인한다.

4. 마치며

 

구현할 프로젝트에 대한 기본적인 설계를 마칩니다. 다음 포스팅에서는 프로젝트 생성을 위해 Gradle을 이용한 Multi Project 생성 방법에 대해서 다루겠습니다.

1. 서론

 

Axon Server는 이벤트 저장소인 EventStore, 어플리케이션 간의 Message 전달하는 역할을 수행합니다.

하지만 AxonFramework를 도입하는데 있어 필수 사항은 아닙니다. AxonIQ에서는 EventStore와 Message Broker를 다른 제품군으로 대체할 수 있도록 지원합니다.

 

따라서 비즈니스 환경에 맞게 취사선택이 가능합니다.

 

AxonIQ에서 제공하는 외부 모듈은 다음과 같으며, 예제 혹은 소스 파일은 깃헙에서 확인하실 수 있습니다.

 

  • Kafka
  • JGroups
  • Spring Cloud
  • Kotlin
  • Mongo
  • AMQP
  • Tracing

저는 AxonFramework + AxonServer를 사용하여 포스팅을 진행하겠습니다.


2. AxonServer 설치

 

1. AxonIQ 홈페이지에 접속후에 Download 버튼을 클릭합니다.

 

 

2. 메일 주소 입력 후에 Download 버튼을 클릭합니다.

 

 

 

 

3. AxonQuickStart.zip 파일을 원하는 위치로 다운로드 후 압축을 풀어줍니다.

 

 

 

4. 압축푼 경로 기준으로 axonquickstart-4.2.2\AxonServer 위치로 이동합니다.

 

 

 

5. 아래 표시된 파일이 우리가 구동해야할 AxonServer 입니다. 생각보다 너무 간단하죠?

 

 

 

6. jar 파일 실행을 위해 도스창을 열도록 하겠습니다. [WINDOW + R] 키를 동시에 누른 후 cmd를 입력합니다. 그리고 확인 버튼을 눌러줍니다.

 

 

 

7. AxonServer 파일 위치로 이동하기 위해서 탐색기 상단의 주소를 복사합니다.

 

 

 

8. 도스창 cd 명령어를 이용하여 Axonserver 위치로 이동합니다.

 

 

9. jar 명령어를 사용하여 Axonserver를 구동합니다.

 

 

 

10. 아래와 같은 화면이 나온다면 정상적으로 실행된 것입니다.

 

참고사항(기본 설정 시, Default 포트 매핑)

 - 메시지 라우팅 : 8124

 - Dashboard : 8024

 

 

11. 정상 수행 확인을 위해 브라우저를 열고, 대시보드 페이지로 접속합니다.

 


3. 마치며

 

AxonFramework를 사용하기 위한 기초 단계 작업을 마쳤습니다.

다음 포스팅부터는 에제 프로젝트 실습을 통해 하나하나씩 개념을 익혀보도록 하겠습니다.

 


Tip)

AxonServer 위치에 axonserver.properties 파일 생성하게 되면 default로 제공되는 속성을 변경할 수 있습니다.

변경 가능한 속성은 Axon 공식 문서를 참고하시기 바랍니다.

 

 

참고로 저는 Event 테스트 후 데이터 삭제를 위해 axoniq.axonserver.devmode.enabled=true 설정하여 사용하고 있습니다.

 

 

개발 모드 적용 후 AxonServer를 기동하게되면, 위 화면과 같이 Development Mode가 활성화되며 Reset Event Store 버튼이 생긴 것을 확인할 수 있습니다.

1. 개요

 

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

 

만약 CQRS와 Event Sourcing에 대해서 궁금하다면 아래 블로그 및 Spring Camp 2017 영상을 참고하시면 좋을것 같습니다.

 

CQRS 소개

CQRS 

 

이벤트 소싱 소개

- 이벤트 소싱(이론부) 영상

- 이벤트 소싱(구현부) 영상

- 이벤트 소싱 소개 블로그

 

 

EventSourcing, CQRS 관련하여 Java 진영에서 사용되는 프레임워크를 검색한 결과, 크게 AxonFramework랑 Eventuate 두 가지를 주로 사용되고 있었습니다.

 

저는 그 중 대중적이고 Spring Boot에 친화적인(?) AxonFramework를 선정하여 공부한 흔적을 남겨보고자 합니다. 포스팅 중간 AxonIQ 기술 블로그 및 웨비나 자료 첨부는 원저작자에게 사용허가를 받았음을 알립니다.


2. Axon Framework 소개

 

출처 : https://youtu.be/GEN0jRkyEtU

 

Axon Framework는 2010년 최초 프로젝트가 시작되었으며, 2015년 이전까지는 관심도가 미비하였지만 이후 MSA가 열풍을 불게되면서 다운로드 수가 폭발적으로 증가하였습니다. 지금은 대략 월간 10만건 정도의 다운로드 수를 기록하고 있으며, 앞으로 점점 더 증가할 것으로 기대하고 있습니다.

 

해당 제품은 오픈소스로써 네덜란드에서 설립된 AxonIQ 회사에서 개발을 주도하고 있습니다. 주력 제품으로는 DDD, EventSourcing, CQRS를 구현할 수 있는 AxonFramework와 EventStore, 마이크로 서비스간 메시지 Routing을 담당하는 Axon Server 입니다.

(※ Axon Server는 Enterprise 버전이 별도로 있으며, 구매시 Cluster 구성 및 Security 설정 등이 가능합니다.)

 

출처 : https://axoniq.io/

 

프레임워크의 아키텍처는 대략 아래와 같습니다.

 

출처 : https://docs.axoniq.io/reference-guide/architecture-overview

 

 

기본적으로 CQRS 구조를 따르고 있습니다. 즉 외부로부터 명령(Command)이 들어오면, 해당 명령에 대한 이력을 EventStore에 저장하고 동시에 Event를 발생시킵니다.

 

발생된 이벤트는 EventHandler를 통하여 Read Model에 반영되고, 사용자 입장에서는 Read Model에 대한 Query를 통하여 데이터를 읽는 구조입니다.

(※ 사용자 편의에 따라 Command와 Query Model은 동일 DBMS에 설정할 수도 분리할 수도 있으며, 종류 또한 다르게 구성할 수 있습니다.)

 

 

 

앞으로 IntelliJ IDE를 사용해서 Spring Boot 기반 AxonFramework을 활용하여 EventSourcing, CQRS, Saga 등을 구현하는 방법에 대해서 포스팅을 진행하려고 합니다.

 

AxonFramework가 DDD(Domain Driven Development), EDD(Event Driven Development)에 기반하고 있으나 샘플코드를 간략하게 작성하기 위해서 DDD스러움은 배제하고(개발 실력, 도메인 지식 부족으로 인한 getter 남용 등....) Axon 제공 기능에 집중하여 소개하도록 하겠습니다.


향후 다룰 주제

  • AxonServer 기본 사용법
  • Gradle을 활용한 Multi Project 설정
  • AxonFramework 프로젝트 기본 설정
  • AxonFramework 데모 프로젝트 구현(계좌 입출금)
  • 기본 아키텍처 소개(Command, Query, Event)
  • Replay
  • Event Tracking Architecture
  • EventStore
  • Version
  • Saga

+ Recent posts