31

I am using a DDD-like approach for a greenfield module of an existing application; it's not 100% DDD due to architecture but I'm trying to use some DDD concepts. I have a bounded context (I think that's the proper term - I'm still learning about DDD) consisting of two Entities: Conversation and Message. Conversation is the root, as a Message doesn't exist without the conversation, and all messages in the system are part of a conversation.

I have a ConversationRepository class (although it's really more like a Gateway, I use the term "Repository") which finds Conversations in the database; when it finds a Conversation it also creates (via Factories) a list of messages for that Conversation (exposed as a property). This seems to be the correct way of handling things as there doesn't seem to be a need for a full-blown MessageRepository class as it only exists when a Conversation is retrieved.

However, when it comes to saving a Message, is this the responsibility of the ConversationRepository, since it's the aggregate root of Message? What I mean is, should I have a method on ConversationRepository called, say, AddMessage that takes a Message as it's parameter and saves it to the database? Or should I have a separate repository for finding/saving Messages? The logical thing seems to be one repository per Entity, but I've also heard "One repository per Context".

2 Answers 2

29

The blue book is definitely worth a read if you want to get the best out of the DDD approach. DDD patterns are not trivial and learning the essence of each of them will help you ponder when to use which pattern, how to divide your application in layers, how to define your Aggregates, and so on.

The group of 2 entities you're mentioning isn't a Bounded Context - it's probably an Aggregate. Each Aggregate has an Aggregate Root, an Entity that serves as a single entry point to the Aggregate for all other objects. So no direct relation between an Entity and another Entity in another Aggregate that is not the Aggregate Root.

Repositories are needed to get hold of Entities that are not easily obtained by traversal of other objects. Repositories usually contain Aggregate Roots, but there can be Repositories of regular Entities as well.

In your example, Conversation seems to be the Aggregate Root. Maybe Conversations are the starting point of your application, or maybe you want to query them with detailed criteria so they are not satisfyingly accessible through simple traversal of other objects. In such a case you can create a Repository for them that will give the client code the illusion of a set of in-memory Conversations to query from, add to or delete from directly. Messages on the other hand are easily obtained by traversal of a Conversation, and you might not want to get them according to detailed criteria, just all of a Conversation's Messages at once, so they might not need a Repository.

ConversationRepository will play a role in persisting Messages, but not such a direct role as you mention. So, no AddMessage() on ConversationRepository (that method rather belongs in Conversation itself) but instead, each time the Repository will persist a Conversation, it's a good idea to persist its Messages at the same time, either transparently if you use an ORM framework such as (N)Hibernate, using ad hoc SQL if you choose so, etc.

2
  • 6
    If an aggregate root, such as Conversation, has many different entity types within it, such as Message, Thingies, and Wingies, then when you save a conversation, e.g. ConversationRepo.save(conversation), how do you know which entities within it need to be saved? In the posters example above, only the message entities need to be saved. Do you traverse all possible collections within the aggregate root to find entities without ids? Commented Jul 23, 2018 at 0:50
  • @chris-richards One way is to put a "Mutation/EventStore" in the Conversation Aggregate Root. When you call conversation.NewMessage() it appends a new Mutation to the MutationStore, and when you calling Repo.save(conversation), you dump it from the Aggregate Root and let Repository decide what method to call for those mutations. Commented Nov 23, 2023 at 12:18
4

You could create the ConversationService and inject the IConversationRepository and IMessageRepository in its constructor. Use repositories for simple CRUD operations, and services for everything else (caching, saving logic, etc.)

1
  • 4
    isn't saving logic CRUD? Commented Jun 19, 2015 at 8:35

Your Answer

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

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