5

Imagine you're designing an application for organizing Workshops. Workshop itself is quite complicated - it acts as a state machine with multiple possible states and transitions between them.

We're using DDD, CQRS and Event Sourcing.

And now, the use cases:

1) one should be able to assign TodoItems to a Workshop and perform CRUD operations on them

2) one should be able to submit additional Resources for a given Workshop (slides, photos, zip/tar.gz archives etc.) and also perform CRUD operations on them

3) one should be able to add a PossibleTerm (with a Lecturer, Date and Room specified) to a Workshop. Also, there should be a way to modify/delete them.

Later on there is a Voting on those terms, system tries to book room for few terms (let's say, for two terms) that won Voting and Users should be split between those Terms.

Here I have following solutions:

1) Model TodoItems, Resources and PossibleTerms as a part of a Workshop.

Pros:

  • everything is where it conceptually belongs to
  • no eventual consistency (not a big problem probably...)

Cons:

  • Workshop aggregate is cluttered with all those CRUD methods for all stuff.
  • According to Implementing Domain Driven Design by Vaughn Vernon, creating huge aggregate roots is an antipattern

2) Create aggregate roots for holding all those "items" (e.g. TodoItemList, PossibleTermList, Resources or whatever you're gonna call them).

Pros:

  • Workshop aggregate is smaller, it's not related with those Lists at all (the only connection would be that for example PossibleTermList holds a reference to WorkshopId)

Cons:

  • when CreateWorkshopCommand arrives, you're forced to create not only a Workshop, but also additional aggregates as they need to exist after Workshop creation. To avoid this, there could be some kind of CreationalSaga, listening for WorkshopCreatedEvent and producing proper commands to create relatives, but I don't think this is a good idea.

3) Model PossibleTerms, Resource, TodoItem as aggregate roots itself

Pros:

  • no need for creating 'aggregate wrappers' (as in 2))

Cons:

  • again, you're cluttering Workshop with at least factory methods
  • it is hard for me to implement term-choosing feature - for counting votes and later on assigning User to a particular Term you need to query your repository for all Terms matching your Workshop ID and so on - this leads to operations on multiple aggregates within one transaction.

Question: which solution sound best for you? Maybe you have differents ideas?

(Bonus question: I need to perform CRUD operations on individual Resources/TodoItem. I'd rather model then as value objects, but how are they then identified?)

6
  • How large is this application? How many classes? A hundred? A thousand? Or a dozen? Is all this ceremony really necessary for an application containing a dozen domain classes? Mar 28, 2015 at 17:27
  • We haven't implemented it yet. Just thinking how to do it at the moment.
    – slnowak
    Mar 28, 2015 at 17:45
  • @slnowak If I were you, I would start with identifying the bounded contexts in your domain. Doing DDD/CQRS/Event Sourcing is a waste of time if you are going to lump everything in a single BC.
    – Songo
    Mar 29, 2015 at 3:08
  • Yeah, great. It is a part of a bigger project. We're indeed modeling a single bounded context.
    – slnowak
    Mar 29, 2015 at 8:08
  • Are there any arguments for using CQRS here? Mar 29, 2015 at 10:12

2 Answers 2

2

I think you're missing the transactional analysis. Which entities will be modified by each of your use cases ? Will some of them impact more than one entity ?

What are your invariants ? Do some of them span multiple entities (e.g., two Terms can't use the same Room at the same date/time) ? Are they true invariants or could eventual consistency be sufficient ?

Vaughn Vernon has a good step by step approach to aggregate modelling here : https://vaughnvernon.co/?p=926

0

It seems to me that the worst choice is trying to model Lists. The point of an aggregate is to ensure a business invariant. What invariant does a collection enforce? If the addItem command is invoked, is the list allowed to say no?

Making all of the entities part of the Workshop aggregate is a natural fit -- especially if the other entities don't "make sense" outside of the context of a workshop. If you've got a business invariant that constrains multiple entities, then you need those entities to be part of the same aggregate.

But it does mean that you've got contention: Alice, working on ToDoItem:1, and Bob, working on ToDoItem:2, can't save their changes at "the same time" because of the write conflict.

Option #3, where each entity is its own aggregate, takes care of the contention problem. Alice and Bob can commit their changes without conflict, the ToDoItems can enforce their own invariant.

It is true that the entities have different life cycles, and you need to be ok with that. You also need to work a bit harder to enforce an invariant that spans multiple aggregates.

The ubiquitous language should offer hints as to whether the components of a workshop need to be part of the same transaction as the workshop itself (if there's already a business process for mitigating contingencies, then you probably don't have a transaction consistency requirement.

That said, if your domain is CRUD (if the model isn't allowed to veto the judgment of the human operators), then trying to invent "aggregates" out of nothing may not be the best play.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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