Events 事件存储、域和敏捷,如何处理

Events 事件存储、域和敏捷,如何处理,events,design-patterns,domain-driven-design,agile,scrum,Events,Design Patterns,Domain Driven Design,Agile,Scrum,在我的工作中,我们为我们的软件平台使用了一个域驱动的CQRS体系结构,带有一个事件存储系统,以便在新版本中发生域更改时可以在域中重新加载事件。到目前为止没有什么特别的 我们的工作协议之一是尽量避免不惜一切代价更改事件,因为这会破坏生产环境中事件存储中事件的旧版本。如果我们真的想改变一个事件,我们必须为旧版本编写转换器,因为这会变得乏味、混乱,有时甚至是不可能的,所以我们尽量避免这种情况 然而,我们也做敏捷软件开发,这对我来说意味着(除其他外)“不要在代码中提前计划太多,只编写在不久的将来实际使用

在我的工作中,我们为我们的软件平台使用了一个域驱动的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等—这些事件导致图书被发运,客户被开票。然后,您的法律部门的某个人添加了一个新要求,即某些标题不能