Validación de la entidad DDD

I have a question related to entity validation. As an example, there is a User which can be registered into a system given email y password. The business rule says that:

  1. email must be valid (must conform to email format) and unique;
  2. password should be between 6 and 20 characters.

My initial thought is to place the validation inside the User.Register(email, password). The main advantage of this approach is that User controls how it is registered by verifying itself the correctness of registration data. The disadvantage is that email uniqueness verification requires calls to UserRepository, Por lo que el User might have dependency on its Repository. To solve this issue, email and password validation might be factored out to some kind of BusinessRule objects. So the validation in User.Register() El método podría verse así:

var emailValidationErrors = _emailRule.Validate(email);
var passwordValidationErrors = _passwordRule.Validate(password);

dónde _emailRule y _passwordRule might be passed as constructor arguments: User(EmailRule emailRule, PasswordRule passwordRule).

In this casse User is not directly coupled to UserRepository. In this way the rules are explicitly shown in the domain, which make it more expressive.

So the question is: what do you think about this approach? Are there any other solutions?

preguntado el 10 de marzo de 12 a las 12:03

4 Respuestas

Podrías implementar un Servicio de dominio that encapsulates this. Typically in DDD you would use a Domain Service when the business logic falls outside of the scope of one individual aggregate; in this case it is the uniqueness check. So, what I'd do is:

public class UserRegistrationService : IUserRegistrationService
{
   private readonly IUserRespository _userRepository;

   public void Register(string email, string password)
   {
        if (!_userRepository.DoesEmailExist(email))
           throw new Exception("Email already registered");

        User user = User.Create(email, password);

        _userRepository.Save(user);
   }
}

Also, if you are worried about User.Create being called outside of the registration service and therefore escaping the uniqueness check, you could possibly set the User.Create method to internal, meaning the only way to create a user is via the RegistrationService.

respondido 12 mar '12, 10:03

If two simultaneous RequestUser-commands are calling this UserRegistrationService to register a user with the same e-mail you would probably end up corrupting your consistency. Aggregates are the reliable sources for enforcing consistency. Of course this simultaneity is not likely to happen, but if it does, you need to undertake measures, otherwise your system gets blown up with bugs. - Luca Nate Mahler

There are three validations that you're trying to do in this example:

  1. Email address must be a valid format;
  2. Email address must be unique (i.e., there isn't an existing user who has that email address);
  3. Password must conform to certain length constraints.

1 and 3 above are simple validations that should be able to be done declaratively on the entity properties (using custom attributes and a suitable validation library in .NET for example).

2 above is the tricky bit, and that's where the intrinsic dependency on the User repository exists in my opinion.

The question is: "Does the responsibility of preventing the creation of a User with the same email address as an existing User lie with the User entity?". I believe the answer to that question is "No" ... it "feels" like this responsibility should lie with a higher-level service or entity for which it is natural to have knowledge of the whole set of users.

So, my take is:

  1. Place those validations that sit with the user inside the User entity (strong cohesion);
  2. Place the uniqueness constraint in a DDD service that is specifically responsible for maintaining the invariants of the set of users--it would do this by wrapping the uniqueness check and the persistence of the new User en una transacción.

Respondido el 11 de diciembre de 12 a las 07:12

You can kind of think there are 2 kinds of validation: internal state validation, and context validation. You can perform internal validation from within that entity, and then perform context validation using some service.

respondido 10 mar '12, 14:03

I liked this approach! This way you are holding Services at the Domain Level, right? - cardosobr

Marcos,

His approach was not bad, but I just do differently.

In my opinion you respect the OCP, putting validation rules out of the Entity, which was wisely decided. Using these validation rules in the class constructor you are suggesting that the rules are immutable, right?

I would not do this way, just create a method dyad setting the rules as this constructor. For me it was not clear what would happen if the validation rules were violated. I like to throw exceptions to the user interface that handles as more ubiquitous warnings.

Another thing that was not clear to me is the event that triggers this validation. it would be when the entity User was added to the repository or have a method of the entity that would do this? I'll take the second option calling the method isValidAuthentication() throwing that exceptions.

Regarding the dependence of the Entity to the Repository, I venture to say that is wrong. You could even make the entity dependent on him, because the repository is a collection of objects, what's wrong with this? However, at this point seems clear that validation is a Service. So if we put these validations in a Service would eliminate this coupling and apply the OCP again. Do you agree?

A big hug and success!

respondido 10 mar '12, 13:03

Thanks for the answer. Regarding exception throwing. I just did not show that code. Of course some ValidationException will be thrown to the caller of User.Register(). This method will be called from a SecurityService which is an application service. Regarding moving validation to a Service.. Well it means that if User.Register() is called outside that service, this might lead to User state corruption, since it cannot control its state. - Markus

Markus, what do you think about the user auth validation Service? I do this to couple lowering. Would be "overengenninering"? - cardosobr

Sorry, I probably don't quite understand you. So you suggest having a Service for validation. In this case how will the process look like? Who will call User.Register() and where the validation will be called? - Markus

I think that the Repository should register the User and not himself, but that only trade couplings, making the Repository to be coupled to the validation Service. I believe that the reverse would be better, creating an object responsible for the Service validation merges with the Repository of User Entity. This object could be a User Factory or other Service. - cardosobr

That's why I asked if this strategy would be "overengineering." Sometimes I fear all you want to apply design patterns at once. I have read a lot about DDD and I feel a bit insecure to assert certain rules. I have worked with an expert and I refer to it often, but only practice will take me to perfection. ;) - cardosobr

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.