在我的工作中,我们为我们的软件平台使用域驱动的CQRS架构,并使用事件存储系统,以便我们可以在新版本中进行域更改时重新加载域中的事件。到目前为止没什么特别的。
我们的一项工作协议是,我们尽量避免不惜一切代价更改事件,因为这会破坏生产环境中事件存储中事件的旧版本。如果我们真的想要更改一个事件,我们必须为旧版本编写转换器,因为这可能会变得乏味,令人困惑,有时甚至不可能,我们会尽可能地避免这种情况。
但是,我们也进行敏捷软件开发,这对我(除其他事项外)意味着“不要在代码中提前做太多计划,只编写实际上将在不久的将来使用的代码” 。在大多数情况下,这对我来说很有意义。
但这成为上述事件的问题。当我创建新事件时,我必须向其添加几乎所有相关数据,以便任何处理事件的系统都具有所需的所有数据。但是,从敏捷的角度来看,我更愿意只将数据添加到我当时真正需要的事件中。
所以我的问题是;处理这种困境的最佳方法是什么?我能想到的唯一“解决方案”是放弃无编辑事件规则,只是接受我们将要编写事件转换器,或尝试为每个事件添加尽可能多的相关数据如果将来数据仍然不足,请创建新事件。
另一种可能性是我们的思维方式存在缺陷。您认为这种方法中最薄弱的环节是什么?有没有更好的方法来处理这个?
我希望这个问题有意义,我期待其他观点:)
答案 0 :(得分:1)
域对象无法猜测任何订阅者的需求。域对象的职责只是生成事件,说明发生了什么。来自@MikeSW的answer
这是我的策略:
a)借助查询组件发布包含基本字段的事件。
例如,我们正在处理酒店评论申请。每个评论只有在获得管理员批准后才能被其他客户查看。
public class CommentApprovedEvent {
private String commentId;
}
事件处理程序更新了评论查询数据的状态。太好了。有时候稍后会有一些进一步的要求,例如在批准通知时,最新批准的评论的内容应该被视为酒店的“推荐”评论。
我们在评论中有hotelId和内容。但这一次,我们选择不将它们添加到活动中。相反,我们使用查询在事件处理程序中检索它:
公共类HotelEventHandler {
public void on(CommentApprovedEvent event) {
CommentDetailDto comment = commentDetailQueryService.
findBy(event.getCommentId());
comment.getHotelId();
comment.getContent();
//update hotel's query data
}
}
有时,甚至无法将所有相关数据添加到活动中。例如,有时候稍后会出现一个新的要求:评论者在批准评论时应该用一些信用来重新评估。但我们在评论中没有评论员的完整档案。所以我们再次选择查询。
b)将重大事件分成较小的事件。 在这种情况下,我们可以添加新事件而不是新属性。考虑DDD样品中的交货案例,交货是货物领域的一个重要价值对象,它显示了给定货物的许多方面:
/**
* The actual transportation of the cargo, as opposed to
* the customer requirement (RouteSpecification) and the plan (Itinerary).
*
*/
public class Delivery {//value object
private TransportStatus transportStatus;
private Location lastKnownLocation;
private Voyage currentVoyage;
private boolean misdirected;
private Date eta;
private HandlingActivity nextExpectedActivity;
private boolean isUnloadedAtDestination;
private RoutingStatus routingStatus;
private Date calculatedAt;
private HandlingEvent lastEvent;
.....rich behavior omitted
}
交货表明货物的当前状态,一旦货物的新处理事件被注册或路线规格发生变化,就会重新计算:
//non-cqrs style of cargo
public void specifyNewRoute(final RouteSpecification routeSpecification) {
this.routeSpecification = routeSpecification;
// Handling consistency within the Cargo aggregate synchronously
this.delivery = delivery.updateOnRouting(this.routeSpecification, this.itinerary);
}
我想到我最初需要一个CargoDeliveryUpdatedEvent,如:
//cqrs style of cargo
public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
apply(new CargoDeliveryUpdatedEvent(
this.trackingId, delivery.derivedFrom(routeSpecification(),
itinerary(), handlingHistory);
}
class CargoDeliveryUpdatedEvent {
private String trackingId;
private ..... //same fields in Delivery?
private ..... //add more when requirements evolves?
}
但最后我发现我可以使用较小的事件来更好地揭示意图,例如:
//cqrs style of cargo
public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
final Delivery delivery = Delivery.derivedFrom(
routeSpecification(), itinerary(), handlingHistory);
apply(new CargoRoutingStatusRecalculatedEvent(this.trackingId,
delivery.routingStatus());
apply(new CargoTransportStatusRecalculatedEvent(this.trackingId,
delivery.routingStatus());
....sends events telling other aspects of the cargo
}
class CargoRoutingStatusRecalculatedEvent{
private String trackingId;
private String routingStatus;
}
class CargoTransportStatusRecalculatedEvent{
private String trackingId;
private String transportStatus;
}
希望这些有帮助。欢呼声。
答案 1 :(得分:0)
您存储活动多长时间了?您的问题是在系统升级期间的短时间“混合模式”操作期间,还是您需要支持多个历史版本事件的某种长期存储模型?
答案 2 :(得分:0)
必须接受“更改”,而不是阻止。您的系统应该有一个内置解决方案,以接受变更作为正常软件生命周期的一部分。反对这可能会“花费”你,而不是采用一种解决方案来接受它作为“功能”。
如果我理解他,我会和Addy一样朝着同一个方向前进。您需要对事件模型进行版本控制。您应该设置一种类型的版本控制事件/软件系统,允许任何新软件版本与旧事件模型兼容。我不认为这是一个不可逾越的问题,但你可能不得不回到你的建筑图纸。