Part 1 was about the theory, now let's see an actual modelling example.
To keep things simple, let's say we're building a banking app and we've identified this desired functionality: "A client can transfer money from one account to another". Let's see... are we changing the business state here? Yes, we do and the result of this operation is that the client's accounts will have different values. So, we need update both accounts: account1.Add(value)
and account2.Substract(value)
and case closed. Right?
It's easy to be superficial and to conclude that we need to modify 2 accounts, but let's take things slowly. We know we are in a "change" operation i.e a command business case, so we need to identify the concepts involved, especially the one in charge of changes. The domain expert tells us that we're dealing with a transfer between 2 accounts or, to be more precise, with an operation which credits one account and debits the other account. Actually, our business case is to register a money transfer and we've already identified 3 concepts: Transfer
, Debit
and Credit
, with the mention that the Transfer is actually defined by at least a Debit and a Credit.
But before we continue, let's identify the change itself: a new transfer that will be part of the banking system state, and we express that change as a domain event named "Transfer Registered". We always express changes as "what happened since the last change" and the past tense is important: it tells us that, from a business point of view, the change is already done. From a technical point of view, we need to persist that change, but that's an implementation detail and the application service's job. Remember that in the real world, changes just happen and they persist, but in the virtual world we need an explicit "save to database".
When modelling, the idea is to try to find out the change first i.e the result of the operation and then to identify all the details we need to end up with that change. At this point we have a couple of concepts,but is that all? Well, no... The domain expert tells us that each transfer should be identifiable (hint!) through a unique number, generated according a specific formula and that we need to know the date and time of creation. And the Debit and Credit must be of different accounts.
We have now enough information to determine our Transfer
aggregate:
- composition:
Transfer number
,Credit
,Debit
,Creation date
- rules: All components are required.
Credit
andDebit
target different accounts.
But this is only the top-level view. We need to dig a bit deeper and to identify what each component means, we want to find out their models. Transfer number
is a group of numbers and letters that must be unique and are determined according to a business formula. Basically a value respecting some constraints.
Debit
is an interesting concept. It's basically a number that needs to be >= 0 but, it needs to be associated with an account or to be more precise, with an account number. Aha! We found another concept. Account number
is a group of numbers generated by a business formula as well. So, Debit
is a composite value of an amount and an Account number
.
Credit
is similar to Debit
only the name is different, but that's enough to be treated as a separated concept which just happens to have the same composition.
Creation date
is just a simple datetime value.
Now, all these components, are value objects from DDD point of view, because each represents a value with a business meaning and unlike a Transfer
they don't need to have an explicit component that acts as an identifier. Their value is their identity.
We see here how we've identified the business concepts, rules and constraints required to make the change and how we've organized them into a group of named models. This is our aggregate, the relevant abstraction responsible for controlling the domain state change.
Onward to part 3: coding - C#