[EN] Mentoring DDD: Validation
We recently came across a problem with validation of domain objects. A the begining I told programmers to use an "
always valid" approach. I thought I was a good starting point and count be later refined. As I supposed, refining would be connected to discovering new requirements connected to
context-bound validation - the kind of validation which is,
according to Jeffrey Palermo, the reason why "always valid" approach is bad.
Surprisingly, the problem came from totally different direction. Direction I wasn't thinking of and, as a result, I was totally unprepared. The problem was connected to uniqueness of some domain object attribute accross all objects of that kind. The concrete example of this issue is
"login" attribute of user account. We all know, and it is obvious, that logins should be unique.
So, how should one implement the business rule saying that login must be unique?
The first solution we thought of was
preventive behaviour in the application service layer.
The "Create account" service would use the dedicated Account Repository method to check if there is already in the database an account with login same as chosen for the account to be created. This check would be made, of course, inside the transaction. If the login is already assigned, the service method would return false to the UI layer. Otherwise, in the same transaction, service layer would create a new Account entity and save it to the database.
There is only one big problem with this not-so-elegant solution.
It simply doesn't work. Lets check the following scenario:
- The thread running "Create account" service method begins its transaction
- Other evil thread begins its own transaction
- Our thread checks if there is already an account with provied login. No, there is not. So proceed with creating account...
- The evil thread creates new account with same login and saves it to the database
- Our thread saves new account to the database. Operation is blocked since the evil thread holds a lock
- The evil thread commits its transaction and releases lock
- The transaction our thread initiated is unblocked, but now there is an account with same login in the database. We get a contraint exception!
What we've learned from this approach is that this problem couldn't be solved using preventive approach unless the transaction isolation level was higher. We don't want to make it higher so we have to rely on exceptions. Eventually what's wrong with exceptions? It is an exceptional case, right?
There is one problem. We've made a decision earlier that we will use
Udi's domain events to communicate validation problems to the UI. Now, should we catch the exception in the service layer and forward it to the UI as a domain event? Domain event that is risen by a service layer? Not an elegant solution, I think.
Does anyone have any ideas how to solve the problem of checking uniqueness of login and stay complaint with DDD approach?