Domain driven design: Services and validations

2019-06-05 01:25发布

问题:

I am following domain driven design rules to design a system. I have doubts regarding services, external systems and validations.

An aggregate must interact with web services of other systems to make validations and to provide information. I am not sure if allowing an aggregate to access the outside word is a good idea. If I create a service to access the external services I have trouble to enforce invariants and validations. If I put all the logic into the aggregate it doesn't sound a good idea but those problems seem to disappear.

To make the problem simpler to understand suppose that there is a User aggregate and it must send an email to make sure the email is correct (in my real problem I have to communicate with external web services)

public class User {
    public User (Long id, String name, String email) {...}
    public changeEmail(String newEmail) {...}
    ...
}

public interface EmailValidatorService {
    /**
    * Sends a test email
    */
    public verifyEmail(String email) throws EmailException;
}

I am not sure if this is this a good idea or if the email verification logic should be part of the User aggregate. Perhaps it could be a service and the User aggregate could use it... but it doesn't sound like a good idea either.

If it is part part of the User aggregate it will have extra responsibilities and if it is a service I do not see an easy way to enforce the domain rules. ¿What if a developer uses changeEmail without validating it with the service?

回答1:

You have a few options to implement this scenario. One is to have an application service which handles this specific use case invoke the validation functionality:

class UserService
{
  EmailValidatorService emailValidatorService;
  UserRepository userRepository;

  public void changeUserEmail(string currentEmailAddress, string newEmailAddress)
  {
    var user = this.userRepository.GetByEmail(currentEmailAddress);
    if (user == null)
       throw ...;


    this.emailValidatorService.verifyEmail(newEmailAddress);

    user.changeEmail(newEmailAddress);

    // commit, etc...
  }
}

An application service is a convenient place to inject validation rules of this sort - the sort where calls to external services are required, or services that the aggregate cannot easily access. More generally, the application service can be a sort of "fallback" mechanism for handling cases where a pure DDD approach doesn't quite fit. Also, an application service can exist regardless of whether you use a domain model or something like transaction script.

Another option is to provide the validator to the aggregate in the changeEmail method on the User class:

class User
{
  string emailAddress;

  public void changeEmail(string newEmailAddress, EmailValidatorService validator)
  {
    validator.verifyEmail(newEmailAddress);
    this.emailAddress = newEmailAddress;
  }
}

The benefit here is that the aggregate encapsulates all of the logic associated with an email address change. As a result, client code cannot change an email address without providing a validator - something the application service approach cannot enforce. Also, this is different from the User aggregate referencing the validator service via dependency injection, which is typically frowned upon. Instead, this is a sort of on-the-fly dependency injection.

Another things to consider in this specific scenario is the nature of the validation your wish to perform. One characteristic is that if an email address is valid at one point, it may no longer be valid in the future. This means that you already need to have workflows in place to handle existing users having an invalid email address. If that logic is already in place, why bother with ensuring validation in the domain at all? After all, even if a validator service ensures an email address is valid and active, there is no guarantee the user will access it.