Implementing Validation
TL;DR
- use Command objects to modify objects of Domain Model
- check basic validation rules on these Commands
- use validation framework for checking rules on Commands
- implement constraints that are “interesting” from business perspective inside Domain Model as part of the behaviour without any validation framework
Constraint scopes
Based on the scope of validity there are three types of constraints we can apply to each object in our Domain Model:
- property/field level
- object level
- application level — across multiple objects
Should all the constraints be checked in our domain layer or is it OK to have some already before domain logic gets executed? One can imagine that the bigger the scope the more complex the rules and hence more logical to end up in Domain Model. However, I find this to be too technical approach and not fully conclusive.
Constraints based on Common Knowledge vs Business Specific Knowledge
Although I believe that in most cases rich Domain Model (as opposed to Anemic Domain Model) is the way to go then I don’t believe that putting all validation there is the best approach. The main reason is that Domain Model forms the essence of the application. The rules that are important from business perspective should be implemented here.
There is a whole plethora of constraints that are not very interesting for the product guy in your team. For example, things like which fields are mandatory and all sorts of rules on the format of input values are of course important but not something that gives you a competitive advantage. In addition to thinking of how “interesting” something can be from the perspective of your Domain Model I find it useful to think of how smart the client has to be to provide the correct value.
For example, lets say that our application exposes an API where you can provide currency code as one input value. If our API spec says that currency code should be ISO 4217 code then that is something that clients can follow quite easily.
However, lets say that we have an API for getting a currency exchange quote like this:
HTTP GET /quotes?source=EUR&target=GBP&sourceAmount=1000
If we have different limits on min and max source amount depending on source and target currency then the client has really no way to figure out what is a valid sourceAmount
. Hence this logic rightly belongs to our business layer and not the interface layer.
In case of common knowledge constraint violation we can return HTTP status code 400
while business rule errors map to 422
.
How to Implement Constraints on Domain Model
I believe that validation frameworks like JSR303 should not be used on Domain Model.
JSR303 allows to implement custom constraints with any scope using special Validators
. However, the problem then is that moving such logic into Validators
means that our Entities and Value Objects become less self-contained and dependent on some external library which in turn makes it harder to see what the Domain Model is about.
For example, we need to implement an event booking application where people can add a Booking
for an Event
. Each Event has limited number of places and Booking
cannot be added when there are not enough places left.
Here is how we could implement it using JSR303:
So far not that bad. Even though the overbooking rule is not applied by Event
then the logic itself is still kind of inside the Entity. But what if we have a rule: if Booking is for more than 10 people then we allow it to go little bit over as big bookings are good for business. When having separate validator and allowing the Event
become (temporarily) invalid we will loose the knowledge of the context in which given action/command was executed.
Hence we get to the point where we see the value of knowing the context in which something should be validated. Here is how we can implement same functionality inside Event without external validator.
Another good thing with having this validation fully in Event is that now we can tie the validation context together with the information of what specific business rule is violated without having to expose all possible rules to the client. So instead of having separate public method for checking each rule we can have single validate
method that encapsulates all possible rule checks:
Separation of Command and Query
Should we have separate method to check for rules and modify the state of Entity? What if client forgets to call validate()
? If we combine both validation and modification as single method then this will violate the command and query separation. Also in many cases one can imagine that having such separate validation or rule check method available separately is valuable for showing a warning to user already before he submits his changes.
I guess if we want to be perfectly strict we should still have some safeguard before modifying. In this case I believe it’s OK to throw an exception. Calling add(booking)
without having called validate(booking)
before is not the intended use of the protocol.
Using commands
In the sample above about booking we used Booking
object as an input parameter. However, there are many cases where input provided via some port is not something that we can easily map into an Entity or Value Object in our Domain Model. The solution here is to make these Commands also part of the model. This way we make it explicit what different operations can be executed on the Domain Model. On practical side this makes it much easier to push important logic out from Controllers
into our domain layer without having to implement very cumbersome methods on our domain services.
For example, we have a use case where user who already has booking for an event wants to book few more seats. To be able to properly check if we can do this we first have to find out how many places did her old booking have and then check if adding more places is allowed.
Some interesting links
http://www.infoq.com/interviews/greg-young-ddd
http://martinfowler.com/articles/replaceThrowWithNotification.html
https://lostechies.com/jimmybogard/2009/02/15/validation-in-a-ddd-world/
http://martinfowler.com/bliki/ContextualValidation.html
P.S. Interested to join us? We’re hiring. Check out our open Engineering roles.
Originally published at https://tech.transferwise.com on December 28, 2015.