6

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.

4
  • Eventual consistency is the way to go here.
    – plalx
    Oct 24, 2015 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, 2015 at 10:15
  • store the trackIds in the User object
    – Ewan
    Oct 26, 2015 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, 2015 at 15:04

1 Answer 1

3

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.

8
  • 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, 2015 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, 2015 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, 2015 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. Oct 26, 2015 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, 2015 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.