使用事件来源时在哪里验证业务规则

时间:2018-07-18 18:25:48

标签: domain-driven-design cqrs event-sourcing

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

但是,将这种方法与事件源结合使用时会出现问题。与事件源系统中的传统方法相比,事件先存储事件,然后在构建实体以执行某些方法时应用所有事件。

基于此,最大的问题是在哪里放置业务逻辑。通常,我希望有一个类似的方法:

public void addNewAppointment(...)

在这种情况下,我希望该方法可以确保没有违反任何业务规则。如果是这种情况,将引发异常。

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

Event event = new AppointmentAddedEvent(...);
event store.save(event);

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

首先,在应用程序层中检查业务规则。 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);

很显然,将约会添加到已取消会议的业务规则不应泄漏到非核心组件中。

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

class Conference {
// ...

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

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

// ...
}

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

你们中有人遇到过类似的问题吗?您如何解决这些问题?

就目前而言,我将只涉及应用程序服务解决方案中的业务规则。它仍然是一个地方,还可以,但是它违反了一些DDD原则。

我期待着您对DDD,事件源和即将到来的更改进行验证的想法和经验。

预先感谢

1 个答案:

答案 0 :(得分:5)

我喜欢这个问题。当我第一次问这个问题时,这是在遵循模式与挑战自我以了解真正发生的事情之间的突破。

  

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

在域实体的方法中,通常的答案是“您之前做过的地方”。您的“第二种方法”是通常的想法。

  

但是违反了关注点分离原则。

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

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

在代码中,看起来像

state = store.get(id)
conf = ConferenceFactory.build(state)
conf.state.appointments.add(...)
store.save(id, conf.state)

我们在事件源中真正要做的就是用持久的事件集合代替可变状态

history = store.get(id)
conf = ConferenceFactory.build(history)
conf.history.add(AppointmentScheduled(...))
store.save(id, conf.history)

在成熟的业务领域(例如会计或银行业务)中,无处不在的语言包括事件历史记录:journalledgertransaction 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) {...}