1

TLDR: Does using DB objects as my AggregateRoot lead to bloated God objects. In comparison to the issues I see in using separate DbObjects and DDD objects.

I'm trying to bring together my understanding of DDD and Clean Architecture, with CQRS via MediatR handlers. All make sense when I read about them separately, then I try to bring them together which looks like it should be fine, but then one brings up fresh questions about another.

DDD Aggregate roots and Persistence is one of those (EFCore in my case). I can think about how I tackle it from a EFCore and typical CRUD setup. And I am starting to understand how I do the DDD side too. But when I try to put them together it seems messy or conflicting.

If I have a Memorial with Names on. I can tackle it from a DDD perspective with a Memorial, and a method to 'RecordName' on that, checking it's not a name that's already been added etc, business rules.

Should I then in my Handler be:

  1. Fetching the Memorial object from the DB/context
  2. Mapping it to a Aggregate
  3. Recording the Name
  4. Mapping it back to a database Memorial object, with a new item on the Recorded name collection
  5. Save to the DB/context

This feels more true to what I understand DDD, but messy from a Persistence point of view. Plus I have DbObjects which could still be violated by being accessed directly.

Alternatively I can create a Memorial Aggregate and Root as my DB Object. That would remove the added complexity of the mapping and potential for someone to still just use the DB objects directly. But from what I've read it suggests that this would start to lead to bloated God objects, which in themselves start to become a real problem.

What is the recommended route here?

2 Answers 2

1

Domain logic

Your TLDR question isn't related to the problem you then elaborate on. I'm going to do my best to address both points.

checking it's not a name that's already been added etc, business rules.

Business logic is domain logic. So if your initial data comes from the database, you perform some business logic, and your end result gets written to the database, then you'll be doing DAL => BLL => DAL, yes.

This feels more true to what I understand DDD, but messy from a Persistence point of view.

I disagree. It's actually quite clean from the DAL point of view, since all you need in the DAL is to have a get and an update method (which you presumably already have for other purposes), and the DAL doesn't need to worry about any logic or how to connect/call these get/update methods.

The alternative would be to implement this business logic in the DAL, which would be messy.

Should I then in my Handler be

If by "in my handler" you mean "directly in my handler method", then no, you shouldn't be accessing the database and doing the mapping.

However, if you mean that the handler calls the persistence layer (which in turn handles the database and the mapping to aggregate), then the answer is yes.

Effectively, something like this is a good implementation in your domain layer:

public void Handle(AddNameToMemorialCommand command)
{
    var memorial = persistence.GetMemorial(command.MemorialId);

    if(!memorial.ContainsName(command.NewName))
    {
        memorial.AddName(command.NewName);
        persistence.UpdateMemorial(memorial);
    }
}

This is an oversimplified example but I hope it highlights the relevant points.


Mapping

  1. Mapping it to a Aggregate
  1. Mapping it back to a database Memorial object

Alternatively I can create a Memorial Aggregate and Root as my DB Object. That would remove the added complexity of the mapping

You can skip a whole lot of mapping logic if you use dependency inversion.

This is close to what I suspect you mean when you say "create a Memorial Aggregate and Root as my DB Object". In essence, your domain defines its aggregate root, and the persistence layer defines its entity as a derived class of that aggregate root.

Note: whether you use an interface instead of a class to derive from is not a distinction I'm going to focus on here.

This means that you don't have to do any mapping, since your domain object will at all times be the same persistence entity. A simple code example:

// in Domain

public class Memorial { ... }

// in Persistence

public class MemorialEntity : Memorial { ... }

// in handler

public void Handle(AddNameToMemorialCommand command)
{
    Memorial memorial = persistence.GetMemorial(command.MemorialId); // persistence returns a MemorialEntity, which is a Memorial subtype

    if(!memorial.ContainsName(command.NewName))
    {
        memorial.AddName(command.NewName);
        persistence.UpdateMemorial(memorial); // This same MemorialEntity object gets passed back to the persistence layer
    }
}

As you can see, you never needed to map the entity to the domain object and back again. It was always the same object, but in the domain layer it was only known by its (base) domain object type, not the (derived) entity type itself.

Even if your persistence layer has separate Memorial and MemorialName entity types, your root entity class (i.e. MemorialEntity) can silently handle those, since then your MemorialName (domain) class would also have an equivalent MemorialNameEntity : MemorialName (persistence) class.

Does using DB objects as my AggregateRoot lead to bloated God objects

In a way, a DDD cynic would generally always argue that aggregate roots are bloated objects, because they specifically bundle as much relevant data as they can.

But just because an aggregate root contains a lot of data does not mean that the persistence layer cannot break this up into multiple entities. The persistence layer gets to store the data however it wants, whether it stores your root object as a massive blob or as a normalized set of tables is a private implementation detail that the domain layer does not care about.

5
  • Effectively, something like this is a good implementation in your domain layer: Did you mean application service layer?
    – Rik D
    Jun 22, 2020 at 11:37
  • @RikD: What I mostly meant was "not your persistence layer", further specification was irrelevant to what this answer focuses on.
    – Flater
    Jun 22, 2020 at 11:40
  • Thank you @Flater - do you know of any good repo/examples/posts of this where I can further my knowledge on combining the elements I mention?
    – David C
    Jun 23, 2020 at 9:22
  • @DavidC: I always really liked Jason Taylor's NorthwindTraders demo codebase. It's a Clean Architecture built on ASP.Net Core and EF Core and does a good job of showcasing how to implement the architecture. It's a bigger example, but there's a lot to learn from it.
    – Flater
    Jun 23, 2020 at 9:32
  • Thanks @Flater I have indeed seen those. While I like them, they seemed quite light on the DDD side.
    – David C
    Jun 23, 2020 at 10:03
0

No. Domain objects are not database records, therefore they should not be mapped to the database.

DDD is about modeling your domain behavior and knowledge. ORMs are about technology. The two don't play on the same field. Now, there are lots of resources unfortunately online that mix these two, where domain objects are directly annotated with some ORM stuff. You can safely ignore those examples.

The same for CQRS. CQRS is also (usually, though not always) on the technology level, not on the domain level. A colleague of mine put it this way: Do the requirements mention commands and queries explicitly separately? If not, it's not in the domain.

Also, it makes no sense to bring these things together unless you have a really good reason to do so. Those reasons should already tell you how you want these things to be brought together, since you are trying to accomplish something. If you don't know what you want to accomplish with each and every one of those then don't do it!

For example the Clean Architecture may be applicable if you want to switch technologies often or even dynamically. You have to support multiple frontends or databases, etc. If not, there is really no good reason to use it.

9
  • The last paragraph (if I understood it correctly) is something I don't agree with. Yes, it's true that if you never change your tech/layers, then the clean coding that simplifies switching out tech/layers (which took time and effort) was never utilized. But that's like saying that as long as you don't get into an accident, there's no good reason to wear a seatbelt. It's technically correct, but that's not a valid justificaiton to not wear a seatbelt when you don't actively plan to get in an accident. The point is that if and when you do get to an accident/need to change tech, you're safe.
    – Flater
    Jun 22, 2020 at 11:00
  • @Flater Clean Architecture is more like carrying a parachute on your back the whole day in case you'll fall down a mountain. Most of us going about our day in the city won't need that. I go even further and say it's like a parachute that you're not really sure will even open. Jun 22, 2020 at 12:04
  • @RobertBräutigam did you read the book or base that opinion on that blog post from someone who admitted to not reading the book either? The ability to change UI or DB is merely a side effect of Clean Architecture.
    – Rik D
    Jun 22, 2020 at 13:20
  • @RikD That is my article, which I wrote before the book came out, but after Uncle Bob already described what it is. But if you imply that I don't know what I'm talking about, here is another article of mine analyzing code from Uncle Bob. Just look at that and tell me if you would write something like that. And if yes, in what sense would that be maintainable at all? Jun 22, 2020 at 18:18
  • @RobertBräutigam: There are many misgivings in your article. To name a few: (1) You're judging it by screaming architecture, not clean architecture (2) "A good top-level design should tell the reader something about the business and not technical details" This is much easier in waterfall than it is in agile (or with a lacking analysis, cynically) (3) "The “http” and “socketserver” packages sound less like part of the business and more like supporting infrastructure stuff" You mean the literal architecture layer as described by Clean Architecture? I wonder why that would be there... [..]
    – Flater
    Jun 23, 2020 at 7:44

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.