Are you suffering from domain anemia? Let's look at what an anemic domain model is and how things can change.
Through my different professional experiences, I had to set a lot of business rules in rich web apps. One day, I stumbled upon a different way to deal with those: using the specification pattern. This method has proven to be structuring and deserves some attention if you do not know what it is.
Let's dig in
Imagine a simple banking application for instance. This app only has clients and bank accounts. A client can have one or multiple accounts, and your job is to create a very simple system of wire transfer between accounts of a same client with this business rule:
- A client cannot transfer money if his account has a balance equals to 0 or less.
- The client associated to his debiting account must be active.
You can clearly see the condition which would prevent a transfer from happening.
In a simple implementation, you would write it this way:
This business rule, although trivial, needs to be implemented every time we want to do a wire transfer. Many constraints arise from such implementation.
First off, if our business rule evolves, we have to change the (or all) class that uses it. Then, this implementation in a if statement is really not explicit at all.
This is where the specification pattern comes into play. The main idea is to isolate the business rule, separating it from its use. It's used for validation, selection and building of business logic.
Mainly three types of specification exist:
- hard coded specifications
- parameterized specifications
- composite specifications
A specification is driven by the following interface:
This type of specification enables us to hard code the business knowledge without having the possibility to modify the business rule from the outside.
A business rule can then be translated this way:
Having created a separated class in order to apply our business rule, we gain clarity and decoupling. Although, it appears obvious that we are condemned to only using our object $account, and that no additional info can be brought from the outside. We still can't use this type of specification in our TransferMoneyCommand because it does not comply totally to our business rule (only the balance is compared).
Parameterized specifications are identical to what we've been talking about, but they resolve the issue we've just mentioned, allowing us to get outside parameters to our candidate.
With this type of specifications, we keep the same pros as before, and we gain flexibility.
This is how our command would look like using our parameterized specification:
To simplify my explanation on parameterized specifications, I've hard coded the instantiation of the class AccountCanTransferMoney. A noticeable improvement of this use would be to inject the specification directly into the command, in order to better unit test our command.
The last type of specification I'd like to take a look into is composite specifications. Such specification bases itself on what we've seen. Indeed, it uses composition to exist. Logical operations between two (or more) specifications are part of composite specifications.
The following example explains the implementation of the AND logical operator:
Then, if we instantiate a composite spec, we can chain it to other specifications (see below), by modifying our previous AccountCanTransferMoney specification:
Finally, here is how we use our composition:
The advantages of this type of specifications are obviously the support of logical operator, and therefore the creation of even more complex business rules. It's now possible to combine specifications. Flexibility is improved, but beware of additional complexity!
Advantages of the specification pattern are as follow:
- Increased decoupling because the responsability of the validation is now limited to an isolated class
- So it is easier to unit test specifications and classes using them
- We made the implicit explicit with a clear definition of business rules