1

So I'm trying to figure out exactly what my aggregates/aggregate roots are in my structure, and can't seem to wrap my head around it.

Database contains the following tables:

SALESGROUP
    ID
    NAME
ITEMNUMBER
    ID
    NUMBER
    SALESGROUPID
DEALER
    ID
    NAME
BUDGETHEADER
    ID
    DATE
BUDGETVERSION
    ID
    BUDGETHEADERID
DEALERBUDGET
    ID
    DEALERID
    BUDGETVERSIONID
DEALERBUDGETLINE
    ID
    DEALERBUDGETID
    SALESGROUPID
    ITEMNUMBERID
    Check constraint: SALESGROUPID IS NULL XOR ITEMNUMBERID IS NULL

For the workflow, the user either creates a BudgetHeader with a single BudgetVersion, or can add a new BudgetVersion to an exiting BudgetHeader. The user can then crate Dealer Budgets. Once those are made, the user can go into them and edit Dealer Budget Lines - while doing so, for each line they'll use cascading dropdowns of Sales Groups and Item Numbers.

So, where are my aggregates here? Note that Sales Group is also used by other entities in this application. This and this would imply to me that, if Sales Group is a Domain Entity, then it should not be shared as part of multiple aggregates. But it's not like I can just put Dealer Budget Lines into Sales Group - it doesn't make sense for Budget Lines to be accessed irrespective of their containing Budget.

Now, it would make sense if we consider Sales Group to be a Value Object - in this case it's immutable and doesn't care how/where it's used. But according to this, since it will need its own repository (we need a repo to get all Sales Groups), it can't be. Further, what if I later add a function in this application to edit Sales Groups? Then they would no longer be immutable and, therefore, would not be value objects.

I'm clearly missing something in my understanding, here. Any idea what it is?

1 Answer 1

3

Maybe you need to step back and take a different look at this. Don't try to be too dogmatic about DDD, and whatever you've read on the Internet about aggregate design, take it with a grain of salt. First, this is how you store data - this is not necessarily exactly representative of your domain concepts; your database structure doesn't have to exactly map to what's happening at runtime. On the other hand, you don't want to ignore the fact that you are using a relational database - you don't want to fight the implementation too much.

Find objects that need to work together to accomplish something. Take into account other concerns such as the way business thinks about these concepts, what is it that they are actually trying to do, access patterns (how users access data, how they manipulate it, are there race-conditions), etc. Define consistency boundaries around these groups of objects: these are your aggregates. Elect some object as the aggregate root, and adjust its interface so that the aggregate can provide all of its services through that object. Because all interaction happens through it, the root can enforce the invariants of the group. Your goal is to (1) minimize and control the (direction of the) coupling between all the objects involved in an implementation of some behavior, and (2) organize your aggregates in a way that aligns with the needs of the business (e.g., I mentioned access patterns before: create aggregates that avoid conflicts when multiple users are working on different aspects of the same thing - to do this, you may need to do things like duplicate some specific aspect of the data across two aggregates, and rely on eventual consistency - I'm literally paraphrasing the example from Evans' book here). As for inter-aggregate references, using IDs has, among other things, the advantage that it makes the aggregate boundaries explicit in code, but it also has some ripple effects, and despite what people claim, you are not required to do it that way - find some way that best suits your needs.
Then figure out how to map these to the database tables; try to make the boilerplate as simple as possible (and consider adjusting the database schema) - again, don't fight the implementation, but find a design that can works with it, while still being able to express domain concepts well.

Now, it would make sense if we consider Sales Group to be a Value Object - in this case it's immutable and doesn't care how/where it's used. But according to this, since it will need its own repositorty (we need a repo to get all Sales Groups), it can't be.

If it's immutable, you can share it. But, if it's a value object, you can also make copies of it; that may make the logic simpler, or align better with the way you designed the aggregates - you'll have to figure out what makes sense for your domain.

It is not required that each aggregate has it's own repository, and it's not forbidden to get value objects through a repository - you just have to be strategic and deliberate about what you expose.

In the original DDD book, in the chapter on Repositories, Evans writes this:

A subset of persistent objects must be globally accessible through a search based on object attributes. Such access is needed for the roots of AGGREGATES that are not convenient to reach by traversal. They are usually ENTITIES, sometimes VALUE OBJECTS with complex internal structure, and sometimes enumerated VALUES.

He states that it usually doesn't make sense to have a repository for values, because if you need one, you can just create a new instance (since they are solely determined by their value). Most of the time, the aggregates will contain values internally, so they'll be loaded together. But sometimes it's convenient to do a query for some value object with a particular property set, or for what's conceptually an enumeration (he doesn't mean a hardcoded enum).

If you consider you domain model to be the encapsulated core of your application, repositories are meant to provide means to get the "surface level" or "entry-point" aggregates; then you can obtain others by traversal. (There's nothing particularly revolutionary about aggregates, it's just normal OOP). In fact, you may want to load a group of related aggregates, and provide one to the client code as a starting point. Now, if you use IDs for inter-aggregate references, then you will need to use repositories for lookup (e.g., Vaughn Vernon recommends loading all the aggregates that you need in advance - see this, Part II, Model Navigation). This is different in execution and has some ripple effects on the surrounding code, but it's not that different conceptually - some object that knows that several aggregates are needed to fulfill some use case, and that knows the relationship between them, loads them all, and hands them over to some peace of code that can perform the required logic.

The internet is full of "rules". People work on different projects that have different nature, and different sets of constraints and requirements - then they share their insights and the designs that worked for them, but the readers often lack context. Then others repeat this and before you know it, it becomes dogma. Don't follow rules, always ask what's the "why" behind them.

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.