0

在我的工作中,我们为我们的软件平台使用了域驱动的 CQRS 架构,并带有一个事件存储系统,以便我们可以在新版本中发生域更改的情况下重新加载域中的事件。到目前为止没有什么特别的。

我们的一项工作协议是我们不惜一切代价尝试避免更改事件,因为这会破坏生产环境中事件存储中的旧版本事件。如果我们真的想改变一个事件,我们必须为旧版本编写转换器,因为这会变得乏味、混乱,有时甚至是不可能的,我们尽量避免这种情况。

但是,我们也进行敏捷软件开发,这对我来说(除其他外)意味着“不要在你的代码中提前计划太多,只编写你将在不久的将来实际使用的代码”。在大多数情况下,这对我来说是有意义的。

但这成为上述事件的问题。当我创建新事件时,我必须向其中添加几乎所有相关数据,以便任何处理事件的系统都拥有它需要的所有数据。但是,从敏捷的角度来看,我更愿意只将数据添加到当时我实际需要的事件中。

所以我的问题是;处理这种困境的最佳方法是什么?我能想到的唯一“解决方案”要么放弃 no-editing-events-rule 并接受我们将不得不编写事件转换器,或者尝试为每个事件添加尽可能多的相关数据如果将来数据仍然不足,则创建新事件。

另一种可能是我们的思维方式存在缺陷。你们认为这种方法中最薄弱的环节是什么?有没有更好的方法来处理这个?

我希望这个问题有意义,我期待其他观点:)

4

3 回答 3

1

域对象不可能猜测任何订阅者的需求。域对象的职责只是生成说明发生了什么的事件。@MikeSW 在这个答案中

这是我的策略:

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;
}

希望这些有所帮助。干杯。

于 2013-12-20T05:49:09.087 回答
0

您将事件存储多长时间?您的问题只是在系统升级期间的“混合模式”操作的短期内,还是您有某种需要支持多个历史版本事件的长期存储模型?

于 2013-02-13T16:08:19.567 回答
0

“改变”必须被接受,而不是被阻止。您的系统应该有一个内置的解决方案来接受更改作为正常软件生命周期的一部分。与此相反,您可能会比实施接受它作为“功能”的解决方案“花费”更多。

如果我对他的理解正确的话,我会朝着与 Addy 相同的方向前进。您需要对事件模型进行版本控制。您应该建立一种版本控制事件/软件系统,该系统将允许任何新软件版本与旧事件模型向后兼容。我不认为这是一个无法克服的问题,但你可能不得不回到你的建筑图纸。

于 2013-12-18T21:45:31.730 回答