Question

At my work we use a Domain Driven CQRS architecture for our software platform, with an event store system so that we can reload events in the domain, in the case of domain changes in new releases. Nothing special so far.

One of our working agreements is that we try to avoid changing events at all cost, since this breaks older versions of the events in the event store on the production environment. If we really want to change an event, we have to write converters for the old versions, and since this can get tedious, confusing and sometimes is not even possible, we try to avoid this as much as we can.

However, we also do Agile software development, which means to me (among other things) 'don't plan ahead too much in your code and only write code that you're actually going to be used in the near future'. This makes sense to me in most cases.

But this becomes a problem with the aforementioned events. When I create new events I have to add pretty much every piece of relevant data to it, so that any system that ever handles the event has all the data it needs. However, from an Agile point of view, I would prefer to only add data to the events that I actually need at that point in time.

So my questions are; what is the best way to handle this dilemma? The only 'solutions' I could think of are either letting go of the no-editing-events-rule and just accept that we're going to have to write event converters, or trying to add as much relevant data as possible to each event and create new events if the data is still insufficient in the future.

Another possibility is that there simply is a flaw in our way of thinking. What would you people consider the weakest link in this approach? Are there better ways of handling this?

I hope this question makes sense and I'm looking forward to other points of view :)

Was it helpful?

Solution

It's impossible for the domain object to guess the needs of whatever subscriber. The domain object's duty is only to generate the event saying what happened. by @MikeSW in this answer

Here is my strategies:

a) Publish events with basic fields with help of query components.

For example, we're working on a hotel comment application. Each comment chould only be viewed by other customers after being approved by admin.

public class CommentApprovedEvent {
    private String commentId;
}

And the event handler updates the status of the comment query data.So far so good. Sometimes later, some further requirements follows, such as when the commment is approved, the latest approved comment's content should be viewed as a "recommended" comment of the hotel.

We do have hotelId and content in the comment. But this time, we choose not to add them to the event. Instead, we use the query to retrieve it in the event handler:

public class HotelEventHandler {

    public void on(CommentApprovedEvent event) {
        CommentDetailDto comment = commentDetailQueryService.
            findBy(event.getCommentId());
        comment.getHotelId();
        comment.getContent();
        //update hotel's query data
    }
}

Sometimes, it's even impossible to add all relevant data to the event. For example, sometimes later, a new requirement comes:the commentor should be rewareded with some credits when the comment is approved. But we don't have commentor's full profile in the comment. So we choose query again.

b) Split big event into smaller ones. In the case, we could add new events instead of new attributes. Consider the delivery case in DDD sample, Delivery is an important value object in the cargo domain wich shows many aspects of a given cargo:

/**
 * 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
}

The delivery indicates the current states of the cargo, it is recalculated once a new handling event of the cargo is registered or the route specification is changed:

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

It came to my mind that I need a CargoDeliveryUpdatedEvent at first, like:

//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?
}

But finally I found out that I could use smaller events which could reveal the intention better, like:

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

Hope these helps. Cheers.

OTHER TIPS

How long are you storing events for? Is your problem just during the short periods of "mixed mode" operation during system upgrades, or do you have some sort of long-term storage model which requires supporting multiple historic versions of events?

"Changes" must be accepted, not prevented. Your system should have a build-in solution to accept change as part of a normal software lifecycle. Going against this might "cost" you more than putting in place a solution accepting it as a "feature".

I would go in the same direction than Addy, if I understood him correctly. You need to version your Event model. You should put in place a kind versionning event/software system that would allow any new software version to be backware compatible with older event model. I don`t think this is a insurmountable problem, but you might have to go back to your architectural drawings.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top