前言:

Event-Driven Microservices 中, 我们会藉由 Aggregate 处理命令 (Command), 产生事件 (Event)。

如图一所示:

  • 当 Aggregate 处理命令后, 会产生事件。
  • 当 Aggregate 所订阅的事件发生时, Aggregate内的状态将发生改变; 由Aggregate 改成 Aggregate’
图一:  Aggregate: Command in, Event out; Event in, Status Changed

在本文中, 我们将迈出开发 Event-Driven Microservices 的第一步:以 given-when-expect 测试驱动的方式, 开发单个的 Aggregate; 我们将以 Axon Framework 实现单个的 Aggregate。

本文:

在本文的例子中, 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 状态的更新写在事件处理常式中。

图二: 面向对象 vs. 事件驱动

现在我们来看看 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, 如预期的, 我们将得到如图三的测试失败的信息:

图三:测试驱动开发; 从失败的测试用例开始进行开发 command-event

回到 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; 这一次如预期的, 测试成功的通过了。

图四:Aggregate Account 的 command-event 如预期的执行

测试用例; 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

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据