我实现了事件源实体(在域驱动的设计中称为聚合)。创建丰富的域模型是一个好习惯。域驱动设计(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,事件源和即将到来的更改进行验证的想法和经验。
预先感谢
答案 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)
在成熟的业务领域(例如会计或银行业务)中,无处不在的语言包括事件历史记录: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) {...}