0

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 :)

Dennisch
  • 6,888
  • 1
  • 17
  • 32
  • Which fields does your events has? And which fields do you change often? – Boris Tsema Feb 14 '13 at 09:55
  • I'm voting to close this question as off-topic because [project management is now off-topic on Stack Overflow](//meta.stackoverflow.com/questions/343829/is-stack-overflow-an-appropriate-website-to-ask-about-project-management-issues/343841#343841). Ask these questions on [SoftwareEngineering.SE](//softwareengineering.stackexchange.com/) and [ProjectManagement.SE](//pm.stackexchange.com/) instead. (You can also flag for moderator intervention to have this question migrated.) – robinCTS Oct 28 '17 at 02:51

3 Answers3

1

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.

Community
  • 1
  • 1
Yugang Zhou
  • 7,123
  • 6
  • 32
  • 60
0

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?

Addys
  • 2,461
  • 15
  • 24
  • We save every event indefinetly. The idea is that we can completely rebuild a domain if the domain logic ever changes. – Dennisch Feb 13 '13 at 16:20
  • In that case, you have a problem: the semantics of your domain will be changing over time, and each version of an event must be coupled to the specific semantics which existed in the domain at the time the event was generated. This is very similar to the classic issues associated with API versioning. – Addys Feb 13 '13 at 16:44
  • In other words, replaying an event stream against version N of your domain will likely not result in the same end-state as replaying the same stream against version N+1, since the business logic which handles those events may have changed. – Addys Feb 13 '13 at 16:53
  • But isn't that the whole point of event sourcing? When the domain logic changes, we want to rerun the events through the domain so that the state gets changed according to to the new domain logic. – Dennisch Feb 13 '13 at 17:01
  • here is an example of the problem, which may or may not be applicable to your domain: lets say we have an online bookstore. when the user clicks the 'purchase' button, you fire off a number of events - CheckInventory, BillCreditCard, CreateShipment, etc - which result in the book being shipped and the customer being billed. Then someone from your legal department adds a new requirement that some titles cannot be sold in certain regions or to certain age groups. So, you now add some new logic to handle that, and also some new events such as "CheckEligibility". (continued...) – Addys Feb 14 '13 at 10:54
  • So, now you have your v.current in which you have already sold title X to customer Y. And you are about to deploy your v.next in which customer Y would not be able to purchase title X due to the new constraints. What happens when you replay in v.next the event stream of customer Y's transaction which originally occurred in v.current? – Addys Feb 14 '13 at 11:01
  • Those look more like commands, events should happen only as their result (InventoryDecreasedEvent, CustomerChargedEvent) - as you aren't billing customer each time you replay the events you shouldn't start to check eligibility when the new logic is introduced. – grudolf Dec 19 '13 at 09:35
0

"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.

Etienne
  • 319
  • 1
  • 5