1

我最近在学习 CQRS,所以我开始了一个带有 axon-framework(一个 java CRQS 框架)的示例项目。

根据快速入门,我得到了以下信息:

public class CreditEntryUnitTests {

    private FixtureConfiguration fixture;

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

    @Test
    public void creditEntryCreated() throws Throwable {
        final Long entryId = 1L;
        final int amount = 100;

        fixture.given().when(new CreateCreditEntryCommand(entryId, amount))
            .expectEvents(new CreditEntryCreatedEvent(entryId, amount));
    }

    @Test
    public void creditEntryMadeEffective() throws Throwable {
        final Long entryId = 1L;
        final int amount = 100;
        final Date start = nov(2011, 12);
        final Date end = nov(2012, 12);// a year effective period

        fixture.given(new CreditEntryCreatedEvent(entryId, amount))
            .when(new MakeCreditEntryEffectiveCommand(entryId, start, end))
            .expectEvents(new CreditEntryMadeEffectiveEvent(entryId, start, end));
    }

    //omitted support methods
}

public class CreditEntry extends AbstractAnnotatedAggregateRoot {

    @AggregateIdentifier
    private Long id;
    private int amount;
    private Date effectiveDateRangeStart;
    private Date effectiveDateRangeEnd;
    private Status status;

    @CommandHandler
    public CreditEntry(CreateCreditEntryCommand command) {
        apply(new CreditEntryCreatedEvent(
            command.getEntryId(), command.getAmount()));
    }

    @EventHandler
    public void on(CreditEntryCreatedEvent event) {
        this.id = event.getEntryId();
        this.amount = event.getAmount();
        this.status = Status.NEW;
    }

    @CommandHandler
    public void markCompleted(MakeCreditEntryEffectiveCommand command) {
        apply(new CreditEntryMadeEffectiveEvent(
            command.getEntryId(), command.getStart(), command.getEnd()));
    }

    @EventHandler
    public void on(CreditEntryMadeEffectiveEvent event) {
        this.effectiveDateRangeStart = event.getStart();
        this.effectiveDateRangeEnd = event.getEnd();
        this.status = Status.EFFECTIVE;
    }

    public CreditEntry() {}

    public enum Status {
        NEW, EFFECTIVE, EXPIRED
    }
}

测试代码促使我使用 axon-framework 编写域模型和集成代码,但它没有涵盖事件产生的副作用。我在哪里测试它们?例如,当信用分录生效时,它的状态应该是有效的。我是否应该在其他测试方法中创建 CreditEntry 实例并通过调用特定的 on(...Event event) 方法进行测试?

还有一个问题是:我应该把业务验证逻辑放在哪里?在命令处理程序方法中?假设如果 CreditEntry 已经生效,则不能再次生效。

@CommandHandler
public void markCompleted(MakeCreditEntryEffectiveCommand command) {
    if (is(NEW)) {
        apply(new CreditEntryMadeEffectiveEvent(
            command.getEntryId(), command.getStart(), command.getEnd()));
    } else {
        throw new IllegalStateException(.......);
    }
}

任何想法都值得赞赏,谢谢。

4

1 回答 1

5

关于你的第一个问题: 你的意思是副作用是聚合对象的内部状态吗?Given-When-Then 夹具测试将聚合视为一种黑盒。所以确实,没有真正需要测试内部状态。应用正确的事件很重要。

例如,您甚至可能最终得到没有任何字段(除了 ID)的聚合,因为您的决策逻辑不依赖于任何内部状态。根据经验,如果我以后需要它来决定应用哪些事件或者它是否更改了事件中应用的数据,我只会将事件中传输的数据保存在聚合对象中。

如果您牢记这一点,您就不必真正测试内部状态。您只需在给定子句中配置具有特定事件的聚合(建立某种状态),然后应用命令。如果出现正确的事件......你就完成了。

关于第二个问题: 业务验证应该在命令处理程序中进行。apply因此,在调用该方法之前,应验证所有内容。原因之一:想象一个系统,其中验证逻辑在整个生命周期内发生变化,但您必须处理在引入系统时输入的旧数据。如果验证将在事件处理程序中,并且验证与首次引入事件时不同,则从事件加载聚合可能会失败,因为“旧”数据与当前验证逻辑不匹配。

于 2013-12-03T13:56:46.100 回答