首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Axon Framework 入門實戰:CQRS/ES 架構下的 Hello World 完整構建

     編輯:Java 綜合教學

CQRS 與 Axon Framework 簡介

CQRS(Command Query Responsibility Segregation)**:命令查詢職責分離,將寫操作與讀操作模型分離,配合**事件溯源**實現狀態重建與審計追蹤。Axon Framework 是 Java 生態成熟的 CQRS/ES 框架,提供命令總線、事件總線、聚合根、事件存儲等核心組件。

項目結構與依賴

demo.axon
├── command      # 命令對象
├── event        # 事件對象
├── handler      # 事件處理器(投影/通知)
├── model        # 聚合根(領域模型)
└── ToDoItemRunner.java  # 啟動入口

Gradle 依賴(核心)

dependencies {
    implementation "org.axonframework:axon-core:2.4.3"
    implementation "org.axonframework:axon-test:2.4.3"
    implementation "org.springframework:spring-context:4.2.3.RELEASE"
    implementation "org.apache.logging.log4j:log4j-slf4j-impl:2.5"
    implementation "javax.persistence:persistence-api:2.1"
    testImplementation "junit:junit:4.12"
}

核心代碼實現

1. 命令:意圖表達

// 创建待辦命令
public class CreateToDoItemCommand {
    @TargetAggregateIdentifier private final String todoId;
    private final String description;
    // 構造器、Getter 省略
}

// 完成待辦命令
public class MarkCompletedCommand {
    @TargetAggregateIdentifier private final String todoId;
    // 構造器、Getter 省略
}

@TargetAggregateIdentifier 標識路由鍵,命令總線據此路由到對應聚合實例。

2. 事件:事實記錄

public class ToDoItemCreatedEvent {
    private final String todoId;
    private final String description;
    // 構造器、Getter
}

public class ToDoItemCompletedEvent {
    private final String todoId;
    // 構造器、Getter
}

事件不可變,僅攜帶業務事實數據,用於狀態變更與異步投影。

3. 聚合根:業務不變量守護者

@Aggregate // Axon 2.x 使用 AbstractAnnotatedAggregateRoot
public class ToDoItem extends AbstractAnnotatedAggregateRoot {
    @AggregateIdentifier private String id;

    protected ToDoItem() {} // 反射重建用

    @CommandHandler
    public ToDoItem(CreateToDoItemCommand command) {
        // 發布事件,而非直接修改狀態
        apply(new ToDoItemCreatedEvent(command.getTodoId(), command.getDescription()));
    }

    @EventHandler
    public void on(ToDoItemCreatedEvent event) {
        this.id = event.getTodoId(); // 事件溯源:從事件重建狀態
    }

    @CommandHandler
    public void markCompleted(MarkCompletedCommand command) {
        apply(new ToDoItemCompletedEvent(id));
    }
    // ToDoItemCompletedEvent 的 @EventHandler 可選,若無狀態變更可省略
}

關鍵點:聚合根**不直接修改字段**,而是通過 apply(event) 發布事件,事件處理器(@EventHandler)更新狀態。這保證了事件溯源的一致性。

4. 事件處理器:讀模型投影/副作用

@Component
public class ToDoEventHandler {
    private static final Logger logger = LoggerFactory.getLogger(ToDoEventHandler.class);

    @EventHandler
    public void handle(ToDoItemCreatedEvent event) {
        logger.info("新待辦創建: {} ({})", event.getDescription(), event.getTodoId());
        // 實際項目:更新查詢數據庫、發送通知、觸發下游流程
    }

    @EventHandler
    public void handle(ToDoItemCompletedEvent event) {
        logger.info("待辦完成: {}", event.getTodoId());
    }
}

5. Spring XML 裝配

<beans xmlns="https://www.springframework.org/schema/beans"
       xmlns:axon="https://www.axonframework.org/schema/core"
       xsi:schemaLocation="... axon-core-2.0.xsd">

    <axon:command-bus id="commandBus"/>
    <axon:event-bus id="eventBus"/>

    <axon:event-sourcing-repository id="toDoRepository"
        aggregate-type="demo.axon.model.ToDoItem"/>

    <axon:aggregate-command-handler id="toDoItemHandler"
        aggregate-type="demo.axon.model.ToDoItem"
        repository="toDoRepository"
        command-bus="commandBus"/>

    <axon:filesystem-event-store id="eventStore" base-dir="events"/> <!-- 文件系统事件存储 -->

    <axon:annotation-config /> <!-- 掃描 @CommandHandler/@EventHandler -->

    <bean class="demo.axon.handler.ToDoEventHandler"/>

    <bean class="org.axonframework.commandhandling.gateway.CommandGatewayFactoryBean">
        <property name="commandBus" ref="commandBus"/>
    </bean>
</beans>

6. 運行入口

public class ToDoItemRunner {
    private final CommandGateway commandGateway;

    public ToDoItemRunner(CommandGateway commandGateway) {
        this.commandGateway = commandGateway;
    }

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("sampleContext.xml");
        new ToDoItemRunner(ctx.getBean(CommandGateway.class)).run();
    }

    private void run() {
        String itemId = UUID.randomUUID().toString();
        commandGateway.send(new CreateToDoItemCommand(itemId, "Need to do this"));
        commandGateway.send(new MarkCompletedCommand(itemId));
    }
}

運行輸出與驗證

12:01:36,113 INFO [main] ToDoEventHandler - 新待辦創建: Need to do this (3126f293-...)
12:01:36,114 INFO [main] LoggingCallback - Command executed: CreateToDoItemCommand
12:01:36,205 INFO [main] ToDoEventHandler - 待辦完成: 3126f293-...
12:01:36,206 INFO [main] LoggingCallback - Command executed: MarkCompletedCommand

單元測試:Fixtures 驗證領域邏輯

public class ToDoItemTest {
    private FixtureConfiguration fixture;

    @Before public void setUp() {
        fixture = Fixtures.newGivenWhenThenFixture(ToDoItem.class);
    }

    @Test public void testCreate() {
        fixture.givenNoPriorActivity()
               .when(new CreateToDoItemCommand("todo1", "implement aggregate"))
               .expectEvents(new ToDoItemCreatedEvent("todo1", "implement aggregate"));
    }

    @Test public void testComplete() {
        fixture.given(new ToDoItemCreatedEvent("todo1", "implement aggregate"))
               .when(new MarkCompletedCommand("todo1"))
               .expectEvents(new ToDoItemCompletedEvent("todo1"));
    }
}

given/when/expectEvents 流式 API 直觀表達:「給定歷史事件 → 當執行命令 → 預期產生事件」。

進階學習資源

  • Axon 官方參考文檔:https://docs.axoniq.io/
  • 實戰項目 Axon Trader:https://github.com/AxonFramework/Axon-trader(含讀模型投影、Saga、分布式命令總線等)
  • Axon 3/4 版本遷移指南:註解包變更(org.axonframework.modelling.command)、簡化配置、Kotlin DSL 支持等。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved