使用事件采购时在何处验证业务规则

Jou*_*ist 7 domain-driven-design cqrs event-sourcing

我实现了事件源实体(在域驱动设计中称为聚合).创建富域模型是一种很好的做法.域驱动设计(DDD)建议尽可能将所有与业务相关的事物放入核心实体和价值对象中.

但是,将此类方法与事件采购结合使用时存在一个问题.与事件源系统中的传统方法相比,首先存储事件,然后在构建实体以执行某些方法时应用所有事件.

基于此,最大的问题是在哪里放置业务逻辑.通常,我想有一个方法,如:

public void addNewAppointment(...)
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我希望该方法可以确保不违反任何业务规则.如果是这种情况,则会抛出异常.

但是在使用事件采购时,我必须创建一个事件:

Event event = new AppointmentAddedEvent(...);
event store.save(event);
Run Code Online (Sandbox Code Playgroud)

现在,我在存储事件之前探索了两种检查业务规则的方法.

首先,检查应用程序层中的业务规则.DDD中的应用程序层是委托层.实际上,它应该不包含业务逻辑.它应该只委托获取核心实体,调用方法和保存的东西.在此示例中,将违反此规则:

List<Event> events = store.getEventsForConference(id);

// all events are applied to create the conference entity
Conference conf = factory.build(events); 

if(conf.getState() == CANCELED) {
    throw new ConferenceClosed()
}

Event event = new AppointmentAddedEvent(...);
event store.save(event);
Run Code Online (Sandbox Code Playgroud)

显然,添加到已取消会议的约会的业务规则不应该泄露到非核心组件中.

我知道的第二种方法是将命令的处理方法添加到核心实体:

class Conference {
// ...

    public List<Event> process(AddAppointmentCommand command) {
        if(this.state == CANCELED) {
            throw new ConferenceClosed()
        }

        return Array.asList(new AppointmentAddedEvent(...));
    }

// ...
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,好处是业务规则是核心实体的一部分.但存在违反分离关注原则的情况.现在,该实体负责创建存储在事件存储中的事件.除此之外,实体负责创建事件感觉很奇怪.我可以争论为什么实体可以处理事件.但是,用于存储而不是用于自然发布的域事件的创建感觉是错误的.

你有没有人遇到过类似的问题?你是如何解决这些问题的?

现在,我将使用应用程序服务解决方案中的业务规则.它仍然是一个地方,但它违反了一些DDD原则.

我期待着您对DDD,事件采购和传入变更验证的想法和经验.

提前致谢

Voi*_*son 6

我喜欢这个问题.当我第一次问到这一点时,那就是在遵循模式之间的突破,并挑战自己去了解真正发生的事情.

最大的问题是在哪里放置业务逻辑

通常的答案是"你之前做过的同一个地方" - 在域实体的方法中.你的"第二种方法"是通常的想法.

但存在违反分离关注原则的情况.

它不是真的,但它看起来确实很奇怪.

在保存当前状态时,请考虑我们通常做的事情.我们运行一些查询(通常通过存储库)以从记录簿中获取原始状态.我们使用该状态来创建实体.然后我们运行命令,实体在其中创建新状态.然后,我们将对象保存在存储库中,该存储库将原始状态替换为记录簿中的新状态.

在代码中,它看起来像

state = store.get(id)
conf = ConferenceFactory.build(state)
conf.state.appointments.add(...)
store.save(id, conf.state)
Run Code Online (Sandbox Code Playgroud)

我们在事件采购中真正做的是用持久的事件集合替换可变状态

history = store.get(id)
conf = ConferenceFactory.build(history)
conf.history.add(AppointmentScheduled(...))
store.save(id, conf.history)
Run Code Online (Sandbox Code Playgroud)

在成熟的业务领域,如会计,银行,无处不在的语言包括事件历史:journal,ledger,transaction history,...之类的事情.在这些情况下,事件历史是域的固有部分.

在其他领域 - 比如日程安排 - 我们不(但是?)在领域语言中有类似的实体,所以当我们改变事件时感觉我们正在做一些奇怪的事情.但核心模式是一样的 - 我们将历史从记录簿中拉出来,我们操纵那段历史,我们将更新保存到记录簿中.

因此,业务逻辑发生在它始终所在的相同位置.

也就是说,域逻辑知道事件.

一个可能有所帮助的练习:放开"面向对象"的约束,只考虑功能......

static final List<Event> scheduleAppointment(List<Event> history, AddAppointmentCommand addAppointment) {

    var state = state(history)

    if(state == CANCELED) {
        throw new ConferenceClosed()
    }

    return Array.asList(new AppointmentAddedEvent(...));
}

private static final State state(List<Event> history) {...}
Run Code Online (Sandbox Code Playgroud)