Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Simon says...

Szymon Pobiega o architekturze i inżynierii oprogramowania
[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:
  1. The thread running "Create account" service method begins its transaction
  2. Other evil thread begins its own transaction
  3. Our thread checks if there is already an account with provied login. No, there is not. So proceed with creating account...
  4. The evil thread creates new account with same login and saves it to the database
  5. Our thread saves new account to the database. Operation is blocked since the evil thread holds a lock
  6. The evil thread commits its transaction and releases lock
  7. 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?

Opublikowane 24 czerwca 2009 12:01 przez simon


# re: [EN] Mentoring DDD: Validation @ 21 lipca 2009 11:16

nice ,but I wonder how isolation can avoid the problem.


# re: [EN] Mentoring DDD: Validation @ 22 lipca 2009 17:39

You have two objects ...an intermediary object say a TemporaryUser where the rule may or may not apply and a final user object where the invariant will always hold true. This can even be done with eventual consistency and the use of compensating actions.

Greg Young

# re: [EN] Mentoring DDD: Validation @ 23 lipca 2009 13:11


Higher (than default Read Commited) isolation levels (Repeatable Read and Serializable) prevent 'evil thread' from inserting its value to the table because our thread have read the value first. Repeatable Read ensures that once obtained, value cannot change (except some special circumstances)


If I understood you well what you said is connected to what Udi said here: http://www.udidahan.com/2009/06/29/dont-create-aggregate-roots/. I should not create aggregate root (User) inside my CreateUser service method but, instead, have a temporary root created elsewhere and only transform my entity as an effect of the service method.


Komentarze anonimowe wyłączone