I'm fairly new to domain driven design and I've been scratching my head for a while over this issue.
Here are my domain rules
- A Record entity has a document of key value pairs
- We can update the document for a record by providing the updated key value pairs
- When we update a document we must calculate some values according to some parsed expressions.
This all needs to happens in-process within a single transaction.
Here are the options I've been reviewing:
Here we use a static class with a 'Raise' method to raise events in-process to be executed synchronously. NB: Reference to complex object in event is ok as we remain in-process.
var record = this.recordRepository.GetRecordById(message.Id);
record.UpdateDocument(Document.Parse(message.Json));
repository.Update(record);
void UpdateDocument(Document updates)
{
this.MergeUpdates(updates);
Events.Raise(new DocumentUpdatedEvent(this));
}
Pros:
- Logic is encapsulated within the Domain in the Entity.
Cons:
- Domain now depends on infrastructure
- May need to take extra steps to guarantee order of handlers of our event if there are more than one.
- Not easy to see exactly what happens at a glance when updating a document
- Domain Events is an overloaded term possible not well used
- Changes to entity occur as side effects
Here we use a static class with an 'Issue' method that allows us to synchronously execute commands.
var record = this.recordRepository.GetRecordById(message.Id);
record.UpdateDocument(Document.Parse(message.Json));
repository.Update(record);
void UpdateDocument(Document updates)
{
this.MergeUpdates(updates);
DomainCommands.Issue(new UpdateExpressionsCommand(this));
}
Pros:
- Logic is encapsulated withing the Domain in the Entity.
- Order of execution is guaranteed
- Easy to see what happens at a glance
Cons:
- Domain depends on infrastructure
- Changes to entity occur as side effects
- Having commands in the domain is unfamiliar territory and questionable
Fairly straightforward... use a domain service and either pass dependencies via constructor or method signature
var record = this.recordRepository.GetRecordById(message.Id);
record.UpdateDocument(Document.Parse(message.Json));
repository.Update(record);
void UpdateDocument(Document updates)
{
this.MergeUpdates(updates);
this.MergeUpdates(this.expressionService.UpdateExpressions(this));
}
- or -
void UpdateDocument(Document updates, IExpressionService expressionService)
{
this.MergeUpdates(updates);
this.MergeUpdates(expressionService.UpdateExpressions(this));
}
Pros:
- No hidden side effects of call to expression service
Cons:
- Entity now has to take a dependency.
- Taking a dependency via method call mixes data and structure
- Expression service will need itself to have a dependency on the parsing engine, so perhaps is not a true Domain Service in this sense
In this approach we let the command handler execute the methods in sequence:
var record = this.recordRepository.GetRecordById(message.Id);
record.UpdateDocument(Document.Parse(message.Json));
var app = this.appRepository.GetByIdentifier(message.AppIdentifier);
record.RecalculateExpressions(app.GetExpressions());
repository.Update(record);
Pros:
- Easy to follow
Cons:
- Need to call Recalculate in any command handler that updates a document.
- Logic has leaked out of the domain