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
- Mapping it to a Aggregate
- 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.