Reputation: 3065
I am using cqrs and ddd to build my application.
I have an account entity, a transaction entity and a transactionLine entity. A transaction contains multiple transactionLines. Each transactionLine has an amount and points to an account.
If a user adds a transactionLine in a transaction that already has a transactionLine that points to the same account as the one the new transactionLine, I want to simply add the new transactionLine amount to the existing one, preventing a transaction from having two transactionLines that point to the same account.
Ex :
Before command :
transaction
transactionLine1(amount=100, account=2)
transactionLine2(amount=50, account=1)
Command :
addNewTransaction(amount=25, account=1)
Desired result :
transaction
transactionLine1(amount=100, account=2)
transactionLine2(amount=75, account=1) // Add amount (50+25) instead of two different transactionLines
instead of
transaction
transactionLine1(amount=100, account=2)
transactionLine2(amount=50, account=1)
transactionLine3(amount=25, account=1) // Error, two different transactionLines point to the same account
But I wonder if it is best to handle this in the command or the event handler.
Before command :
transaction
transactionLine1(amount=100, account=2)
transactionLine2(amount=50, account=1)
Command :
addNewTransaction(amount=25, account=1) // Detects the case
Dispatches event
transactionLineAmountChanged(transactionLine=2, amount=75)
AddTransactionLine command is received
Check if a transactionLine exists in the new transactionLine's transaction with the same account
If so, emit a transactionAmountChangedEvt event
Otherwise, emit a transactionAddedEvt event
Corresponding event handler handles the right event
Before command :
transaction
transactionLine1(amount=100, account=2)
transactionLine2(amount=50, account=1)
Command :
addNewTransaction(amount=25, account=1)
Dispatches event
transactionLineAdded(transactionLine=3, amount=25)
Handler // Detects the case
transactionLine2.amount = 75
AddTransactionLine command is received
TransactionLineAdded event is dispatched
TransactionLineAdded is handled
Check if the added transaction's transactionLine points to the same account as an existing transactionLine in this account
If so, just add the amount of the new transactionLine to the existing transactionLine
Otherwise, add a new transactionLine
Upvotes: 4
Views: 2079
Reputation: 2440
Think of commands and events as 'containers', 'dtos' of data that you are going to need in order to hydrate your AggregateRoots or send out to the world (event) for other Bounded Contexts to consume them. That's it. Any other operation that is strictly related to your Domain has no place but your AggregateRoots, Entities and Value Objects.
You can add some 'validation' to your Commands, either by using DataAnnotations or your own implementation of a validate method.
public interface ICommand
{
void Validate();
}
public class ChangeCustomerName : ICommand
{
public string Name {get;set;}
public void Validate()
{
if(Name == "No one")
{
throw new InvalidOperationException("Sorry Aria Stark... we need a name here!");
}
}
}
Upvotes: 1
Reputation: 309
Neither commands nor events should contain domain logic, only the domain should contain the domain logic. In your domain, aggregate roots represent transaction boundaries (not your transaction entities, but transactions for the logic). Handling logic within commands or events would bypass those boundaries and make your system very brittle.
The right place for that logic is the transaction entity.
So the best way would be
AddTransactionCommand finds the correct transaction entity and calls
Transaction.AddLine(...), which does the logic and publishes events of what happened
TransactionLineAddedEvent or TransactionLineChangedEvent depending on what happened.
Upvotes: 3