I want to create a little GPS tracking program. Simplified: Users can create Tracks. To make things scale Track and User are two separate AR's. Track contains an AuthorUser which it refers to by UserID.

The following rules apply:

  1. A User can create an unlimited amount of Tracks.

  2. Users can be removed from the system. In that case all the created Tracks of the User need to be removed.

I want to use DDD+ES for this. Knowing the AR's can only be created/loaded by ID, how should I delete/modify all Tracks when a UserRemovedEvent is triggered?

Please note the query model is completely decoupled and might lag in time since it is async event-based updated.

  • Eventual consistency is the way to go here. – plalx Oct 24 '15 at 14:04
  • Won't using only eventual consistency in this case require some query repository? I want to avoid this as views might be updated asynchronously. – Pepster Oct 26 '15 at 10:15
  • store the trackIds in the User object – Ewan Oct 26 '15 at 11:04
  • But that will break performance: "A User can create an unlimited amount of Tracks." as it will create a huge transaction boundary... – Pepster Oct 26 '15 at 15:04

From what I can see, the answer to what you asked is a Saga -- you are creating transactions for multiple different Aggregate Roots, so no one AR has the responsibility for all of it. The Saga uses the userId to query the TracksRepository/Projection for the list of tracks to be removed, and enumerates them. Note that the Saga is relying only on model api -- it listens to the events, and sends commands to the Tracks, but has no knowledge of the internals of either.

Note the assumption here -- if these are different aggregate roots, then they are going to be modified in different transaction boundaries, and you're only getting "eventual" consistency. So make sure that you and your domain experts agree what the real latency requirement is.

  • One also needs to be able to handle some client accessing retired ID's (whether eventual or not). Some designs will keep the ID's yet mark them as retired (to ensure they never get reused, and/or to differentiate between invalid ID's and retired ID's). – Erik Eidt Oct 23 '15 at 17:28
  • @ErikEidt: I use UUID's, so accidentally re-using an ID is a pretty uncommon scenario. Also, retired tracks are removed from the query repos, so they will not be visible to other users. – Pepster Oct 26 '15 at 10:17
  • @VoiceOfUnreason, Saga, interesting! But I don't get this part: "The Saga uses the userId to query the TracksRepository/Projection for the list of tracks to be removed, and enumerates them." How do I do that if I use pure ES? The Track Aggregate can only be "inflated" from replaying the events for a specific TrackID, so no fancy querying possible... Or do I miss something? – Pepster Oct 26 '15 at 10:24
  • 1
    I think you are missing that the saga is working from the read model. If there was infinite memory, you would always have the list of tracks that the saga needs available - the saga would just use UserRemovedEvent.userId to look everything up. You probably don't have infinite memory, though, so you either (a) persist the result set you need on write, and then load the data you need from the persistent store on read or (b) read all the user's events to get the list of trackIds you need, and reload the events for each track. – VoiceOfUnreason Oct 26 '15 at 13:22
  • 1
    Just a little side warning: there's been a little shake in the DDD/CQRS community around the terms Saga and Process Managers (as defined in Enterprise Integration Patterns). In some places they mean the same thing, somewhere else they mean different things. Be prepared to bump into some inconsistencies, when digging for information. – ZioBrando Oct 27 '15 at 9:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.