Events 事件存储、域和敏捷,如何处理
在我的工作中,我们为我们的软件平台使用了一个域驱动的CQRS体系结构,带有一个事件存储系统,以便在新版本中发生域更改时可以在域中重新加载事件。到目前为止没有什么特别的 我们的工作协议之一是尽量避免不惜一切代价更改事件,因为这会破坏生产环境中事件存储中事件的旧版本。如果我们真的想改变一个事件,我们必须为旧版本编写转换器,因为这会变得乏味、混乱,有时甚至是不可能的,所以我们尽量避免这种情况 然而,我们也做敏捷软件开发,这对我来说意味着(除其他外)“不要在代码中提前计划太多,只编写在不久的将来实际使用的代码”。在大多数情况下,这对我来说是有意义的 但这成为上述事件的一个问题。当我创建新事件时,我必须向其中添加几乎所有的相关数据,以便任何处理该事件的系统都拥有它所需的所有数据。然而,从敏捷的角度来看,我更愿意只向我当时实际需要的事件添加数据 所以我的问题是,;处理这一困境的最佳方式是什么?我能想到的唯一“解决方案”是,要么放弃不编辑事件规则,接受我们将不得不编写事件转换器的事实,要么尝试向每个事件添加尽可能多的相关数据,并在将来数据仍然不足时创建新事件 另一种可能是我们的思维方式存在缺陷。你认为这一方法中最薄弱的环节是什么?有没有更好的处理方法Events 事件存储、域和敏捷,如何处理,events,design-patterns,domain-driven-design,agile,scrum,Events,Design Patterns,Domain Driven Design,Agile,Scrum,在我的工作中,我们为我们的软件平台使用了一个域驱动的CQRS体系结构,带有一个事件存储系统,以便在新版本中发生域更改时可以在域中重新加载事件。到目前为止没有什么特别的 我们的工作协议之一是尽量避免不惜一切代价更改事件,因为这会破坏生产环境中事件存储中事件的旧版本。如果我们真的想改变一个事件,我们必须为旧版本编写转换器,因为这会变得乏味、混乱,有时甚至是不可能的,所以我们尽量避免这种情况 然而,我们也做敏捷软件开发,这对我来说意味着(除其他外)“不要在代码中提前计划太多,只编写在不久的将来实际使用
我希望这个问题有意义,我也期待其他观点:)您将事件存储多长时间?您的问题是仅仅在系统升级期间的短期“混合模式”操作期间出现的,还是您有某种需要支持多个历史版本事件的长期存储模型?必须接受而不是阻止“更改”。您的系统应该有一个内置解决方案,以接受作为正常软件生命周期一部分的更改。与此相反,您可能会付出比将其作为“特性”接受的解决方案更多的“成本” 如果我理解正确的话,我会和艾迪走同一条路。您需要对事件模型进行版本设置。您应该建立一种版本化事件/软件系统,允许任何新的软件版本与旧的事件模型兼容。我不认为这是一个无法克服的问题,但你可能不得不回到你的建筑图纸上 域对象不可能猜测任何订户的需求。域对象的职责只是生成表示发生了什么的事件。由@MikeSW在本书中 以下是我的策略: a)在查询组件的帮助下,使用基本字段发布事件。 例如,我们正在开发一个酒店评论应用程序。每个评论只有在得到管理员批准后才能被其他客户查看
public class CommentApprovedEvent {
private String commentId;
}
事件处理程序会更新注释查询数据的状态。到目前为止还不错。有时,随后会有一些进一步的要求,例如,当该评论获得批准时,最新批准的评论内容应被视为酒店的“推荐”评论
我们在评论中确实有hotelId和内容。但这一次,我们选择不将它们添加到事件中。相反,我们使用查询在事件处理程序中检索它:
芬坦德勒公共级酒店{
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);
}
我首先想到我需要一个CargoDeliveryUpdateEvent,比如:
//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;
}
希望这些有帮助。干杯。我们会永久保存每个事件。我们的想法是,如果域逻辑发生变化,我们可以完全重建一个域。在这种情况下,您会遇到一个问题:域的语义将随着时间的推移而变化,并且事件的每个版本都必须与域中存在的特定语义相耦合me事件已生成。这与API版本控制相关的经典问题非常相似。换句话说,针对域的版本N重放事件流可能不会导致与针对版本N+1重放相同流相同的结束状态,因为处理这些事件的业务逻辑可能已更改。但不是“这不是事件源的全部意义吗?当域逻辑更改时,我们希望通过域重新运行事件,以便根据新的域逻辑更改状态。下面是一个问题示例,它可能适用于您的域,也可能不适用于您的域:假设我们有一个在线书店。当用户单击“购买”按钮时“按钮,您触发了一系列事件—检查库存、BillCreditCard、CreateShipping等—这些事件导致图书被发运,客户被开票。然后,您的法律部门的某个人添加了一个新要求,即某些标题不能