2

My domain aggregate is struggling with simple business logic so I feel like I have modeled it incorrectly.

The architecture I have right now uses domain driven design with event sourcing. The domain aggregate produces events which get digested by subscribers. Eventually the event queue will be empty and the view store completely updated.

Where it struggles is with large collections of objects on the aggregate and the need to coordinate between domains.

So one aggregate is a ChatAggregate which allows people to add comments. However a user can only add one comment per hour or one comment for the entire life time. My domain object cannot keep all comments in memory but I still need my ChatAggregate to apply this business logic. Because of the eventually consistent nature of my view store, I cannot let my aggregates query it for business logic. How does DDD handle when an aggregate needs access to a lot of data?

The second issue I have is when a domain command is being processed, I want to query other aggregates before processing the command with the target aggregate. An example is before creating a new ChatCommentAggregate instance, I would like to query the ChatAggregate to see if the chat is still open.

migrated from stackoverflow.com Jul 27 '15 at 14:27

This question came from our site for professional and enthusiast programmers.

  • 2
    possible duplicate of DDD - Aggregate root with large number of children – durron597 Jul 27 '15 at 14:29
  • 2
    Also possibly a duplicate of this – durron597 Jul 27 '15 at 14:30
  • Please don't put 'Aggregate' at the end of your class names... You don't use StringAggregate or IntegerPrimitive either. It's confusing. That being said, is this all the same bounded context? If so, what is your aggregate root? – JDT Jul 28 '15 at 7:30
  • I don't, but I thought it read better in this example – LivingOnACloud Jul 28 '15 at 8:04
  • @durron597 They are similar but not the same. I've come across their problems before and solved them in the same way. In their solutions there is no reason to store lots of data so the solution represented that. But for me, I need access to all comments for a given user inside of a chat. – LivingOnACloud Jul 28 '15 at 8:11
1

If eventual consistency will not do then you'll need a way to store 100% consistent data on the transactional/write/domain side of things.

That being said, it is sometimes possible to implement a business rule slightly differently but still get the same result. For instance, you could add a list of ChatAggregateComment value objects to your ChatAggregate that store, say, the UserId, CommentId, and DateCommented. This list would grow only as big as the number of people involved in the chat. However, if even that is going to be too large then you could denormalize the data and store this same list as immediately consistent on your write side and then query table through some domain-side query interface.

As far as creating a new ChatCommentAggregate is concerned you could use your ChatAggregate as the factory for a ChatCommentAggregate. Your rule could be applied within the ChatAggregate.CreateComment method.

  • What do terms 'write side' and 'domain side' represent? – robotron Jan 25 '18 at 7:46
  • @robotron: mmm... that does read somewhat odd. The transactional store is heavily related to your domain whereas the query/read store is for retrieving, typically, demormalized data. In this case the read store could be kept 100% consistent with the transactional store. If the domain requires access to the read data it will need to either be passed to the domain as VOs or the domain would need to retrieve it through some query interface or ACL of sorts. – Eben Roux Jan 25 '18 at 10:01
0

So one aggregate is a ChatAggregate which allows people to add comments. However a user can only add one comment per hour or one comment for the entire life time. My domain object cannot keep all comments in memory but I still need my ChatAggregate to apply this business logic. Because of the eventually consistent nature of my view store, I cannot let my aggregates query it for business logic. How does DDD handle when an aggregate needs access to a lot of data?

It re-examines the design to see if the actual requirement is being expressed correctly. A big part of the value prop of DDD is that when the programmers are having trouble fitting the business requirements into the the design, it often means that the programmers don't understand the real requirements.

To extend your example; you might discover that CommentSubmitted and CommentPublished are actually two different events, and that there is a business process responsible for figuring out if one event necessarily leads to the other.

The write model might look like:

User.submitComment(comment) ->
    [ CommentSubmitted
    , UserMuted
    ] ;

Chat.publishComment(comment) ->
    [ CommentPublished
    ] ;

Where submitComment throws an exception if the muted state of the user doesn't support commenting, and the Chat refuses to publish comments where the user has already done so.

The glue that makes this work is a state machine that is listening to the event stream (warning: pseudo-code ahead).

IDLE {
    onCommentSubmitted(event) {
        asyncDispatch(publishComment(event));
        asyncDispatch(scheduleTimer(event));
        transitionTo(PUBLISHING)
    }
    onTimer(event) {
        asyncDispatch(unmuteUser(event));
    }
}
PUBLISHING {
    onCommentPublished(event) {
        transitionTo(IDLE)
    }
}

This is a process manager; it's interacting with the domain purely through the api (listening to events, and dispatching commands). It's also scheduling messages to its future self (via the timer).

Note that, as shown here, it doesn't generally get to invoke commands on the domain model directly. The problem is that you are only supposed to update one history per transaction, and the process manager needs to persist its own state transitions.

It's worth noting too the space that has opened up, to push on the requirements: do we mute the user when they submit a comment (and then try to recover if the comment isn't published) or do we only mute the user if a comment is published. Maybe we only mute the user for a few minutes when they submit, but for the full day once we have the acknowledgement that the comment is published, and so on.

The second issue I have is when a domain command is being processed, I want to query other aggregates before processing the command with the target aggregate.

Go right ahead -- that's a perfectly reasonable thing for the application to do. You need to keep in mind that any query you run returns stale results -- you can't ever know what's "now" except within the domain aggregate itself, but "not too long ago" is almost always going to be good enough.

A common pattern for validation is that the human operator (sitting in front of the client), will try to verb, and the app will say "nope - the latest information we have says that we can't do that." But if everything checks out, then the command is dispatched to the server, which can do its own validation prior to sending the command to the aggregate for processing.

But once you are in the aggregate, the only things you should be looking at are the current state of the aggregate, and the state captured in the command.

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.