
前言:
Event-Driven Microservices 中, 我们会藉由 Aggregate 处理命令 (Command), 产生事件 (Event)。
如图一所示:
- 当 Aggregate 处理命令后, 会产生事件。
- 当 Aggregate 所订阅的事件发生时, Aggregate内的状态将发生改变; 由Aggregate 改成 Aggregate’


在本文中, 我们将迈出开发 Event-Driven Microservices 的第一步:以 given-when-expect 测试驱动的方式, 开发单个的 Aggregate; 我们将以 Axon Framework 实现单个的 Aggregate。
given when expect :
在本文的例子中, Aggregate Account 主要是处理客户银行帐号的取款。
首先, 我们以 Kotlin 来高效的定义 Aggregate Account 内的命令与事件。
class CreateAccountCommand(val accountId : String, val balance : Int)
class WithdrawMoneyCommand(@TargetAggregateIdentifier val accountId : String, val amount : Int)
class AccountCreatedEvent(val accountId : String, val balance : Int)
class MoneyWithdrawnEvent(val accountId: String, val amount: Int, val balance: Int)
接下来我们先来谈谈事件驱动 ( Event-Driven) 与面向对象的开发方式之间的差异; 使得大家能平顺的进入事件驱动开发的世界里。
如图二所示:
在面向对象的世界里, 我们往往会在类的方法中, 同时完成业务逻辑的校验与类的状态的更新。
在事件驱动的世界里, 我们会将业务逻辑的校验 (判断)、Aggregate 状态的更新, 分别写在不同的 Aggregate 的方法中; 业务逻辑的校验 (判断) 写在命令处理常式中, Aggregate 状态的更新写在事件处理常式中。

现在我们来看看 Aggregate Account:
在事件驱动微服务的开发, 我们将采用 given-when-expect 测试驱动开发的方式; 以确保 Aggregate 中的每一个的命令、事件、状态的改变都能如预期。
我们在 setup() 中建立起我们所需的 Test Fixture; Aggregate Account。也就是说, 将以 Aggregate 的状态为基线, 并由 Aggregate Account 来执行测试、产生特定的测试结果。
测试用例; testCreateAccount; 将测试 Aggregate Account 在执行完命令CreateAccountCommand 后, 是否会产出事件 AccountCreatedEvent ?
public class AccountTest {
private AggregateTestFixture<Account> fixture;
@Before
public void setUp() throws Exception {
fixture = new AggregateTestFixture<>(Account.class);
}
@Test
public void testCreateAccount() {
fixture.givenNoPriorActivity()
.when(new CreateAccountCommand("1234", 10000))
.expectEvents(new AccountCreatedEvent("1234", 10000));
}
执行测试用例 testCreateAccount, 如预期的, 我们将得到如图三的测试失败的信息:

回到 Aggregate Account, 开始开发:
- 命令 CreateAccountCommand的处理常式。
- 事件 AccountCreatedEvent 的处理常式。
- 第一步: 我们宣告 Aggregate Account 的 Aggregate ID 是 accountId; 每个Aggregate 都必须要有一个 Aggregate ID。
@NoArgsConstructor
public class Account {
@AggregateIdentifier
private String accountId;
- 第二步: 我们让命令 CreateAccountCommand 的处理常式; Account; 产出事件 AccountCreatedEvent。
@NoArgsConstructor
public class Account {
@AggregateIdentifier
private String accountId;
private int balance;
@CommandHandler
public Account(CreateAccountCommand command) {
apply(new AccountCreatedEvent(command.getAccountId(), command.getBalance()));
}
- 第三步: 事件 AccountCreatedEvent 的处理常式, 将更新 Aggregate Account 的状态; 获取新产生的 Account 的 accountId, balance。
@NoArgsConstructor
public class Account {
@AggregateIdentifier
private String accountId;
private int balance;
@EventSourcingHandler
public void on(AccountCreatedEvent event) {
this.accountId = event.getAccountId();
this.balance = event.getBalance();
}
开发完了 Aggregate Account 的命令 CreateAccountCommand 的处理常式、事件 AccountCreatedEvent 的处理常式, 我们再一次的执行测试用例; testCreateAccount; 这一次如预期的, 测试成功的通过了。

测试用例; testWithdrawAmount; 将测试关于 Aggregate Account 的取款的command-event。
测试用例; testWithdrawAmount; 将会:
- given: 创建执行测试用例的前置条件: 产生事件 AccountCreatedEvent; 表示某个 Account 已成功的建立。
- when: 创建执行测试用例时的状态: 产生命 WithdrawMoneyCommand。
- expectEvents: 预期命令 WithdrawMoneyCommand 会产生的事件: 将产出 MoneyWithdrawEvent。
public class AccountTest {
private AggregateTestFixture<Account> fixture;
@Test
public void testWithdrawAmount() {
fixture.given(new AccountCreatedEvent("1234", 1000))
.when(new WithdrawMoneyCommand("1234", 600))
.expectEvents(new MoneyWithdrawnEvent("1234", 600, 400));
}
再回到 Aggregate Account 的开发:
- 命令 WithdrawMoneyCommand 的处理常式。
- 事件 MoneyWithdrawnEvent 的处理常式。
我们将业务逻辑的校验 (判断); 取款金额是否超过帐户内的馀额; 写在命令
WithdrawMoneyCommand 的处理常式中。
当取款金额没超过帐户内的馀额时, 命令 WithdrawMoneyCommand 的处理常式便会产生事件 MoneyWithdrawnEvent。
@NoArgsConstructor
public class Account {
@CommandHandler
public void handle(WithdrawMoneyCommand command) throws OverdraftLimitExceedeException {
if (balance >= command.getAmount()) {
apply(new MoneyWithdrawnEvent(accountId, command.getAmount(), balance - command.getAmount()));
} else {
throw new OverdraftLimitExceedeException();
}
}
事件 MoneyWithdrawnEvent 的处理常式, 变更 Aggregate Account 的状态; 变更帐户内的馀额。
@NoArgsConstructor
public class Account {
private int balance;
@EventSourcingHandler
public void on(MoneyWithdrawnEvent event) {
this.balance = event.getBalance();
}
}
结论:
Event-Driven Microservices 将可使得我们的软件架构得到真正的扩展。
而 Event-Driven Microservices 在开发上也并不如想像中的奇怪与抽象; 只需关注下:
- 将业务逻辑的校验 (判断) 与状态的更新, 进行代码上的隔离。
- 以 given-when-expect 测试驱动开发的方式, 一步一步的确认 command-event-status (命令-事件-状态) 都能符合预期。
参考资料:
Axon Framework: https://axoniq.io