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

时间:2013-02-13 16:02:26

标签: events design-patterns domain-driven-design agile scrum

在我的工作中,我们为我们的软件平台使用域驱动的CQRS架构,并使用事件存储系统,以便我们可以在新版本中进行域更改时重新加载域中的事件。到目前为止没什么特别的。

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

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

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

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

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

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

3 个答案:

答案 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一样朝着同一个方向前进。您需要对事件模型进行版本控制。您应该设置一种类型的版本控制事件/软件系统,允许任何新软件版本与旧事件模型兼容。我不认为这是一个不可逾越的问题,但你可能不得不回到你的建筑图纸。