Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to put global rules validation in DDD

I'm new to DDD, and I'm trying to apply it in real life. There is no questions about such validation logic, as null check, empty strings check, etc - that goes directly to entity constructor/property. But where to put validation of some global rules like 'Unique user name'?

So, we have entity User

public class User : IAggregateRoot {    private string _name;     public string Name    {       get { return _name; }       set { _name = value; }    }     // other data and behavior } 

And repository for users

public interface IUserRepository : IRepository<User> {    User FindByName(string name); } 

Options are:

  1. Inject repository to entity
  2. Inject repository to factory
  3. Create operation on domain service
  4. ???

And each option more detailed:

1 .Inject repository to entity

I can query repository in entities constructor/property. But I think that keeping reference to repository in entity is a bad smell.

public User(IUserRepository repository) {     _repository = repository; }  public string Name {     get { return _name; }     set      {        if (_repository.FindByName(value) != null)           throw new UserAlreadyExistsException();         _name = value;      } } 

Update: We can use DI to hide dependency between User and IUserRepository via Specification object.

2. Inject repository to factory

I can put this verification logic in UserFactory. But what if we want to change name of already existing user?

3. Create operation on domain service

I can create domain service for creating and editing users. But someone can directly edit name of user without calling that service...

public class AdministrationService {     private IUserRepository _userRepository;      public AdministrationService(IUserRepository userRepository)     {         _userRepository = userRepository;     }      public void RenameUser(string oldName, string newName)     {         if (_userRepository.FindByName(newName) != null)             throw new UserAlreadyExistException();          User user = _userRepository.FindByName(oldName);         user.Name = newName;         _userRepository.Save(user);     } } 

4. ???

Where do you put global validation logic for entities?

Thanks!

like image 215
Sergey Berezovskiy Avatar asked Apr 28 '11 12:04

Sergey Berezovskiy


1 Answers

Most of the times it is best to place these kind of rules in Specification objects. You can place these Specifications in your domain packages, so anybody using your domain package has access to them. Using a specification, you can bundle your business rules with your entities, without creating difficult-to-read entities with undesired dependencies on services and repositories. If needed, you can inject dependencies on services or repositories into a specification.

Depending on the context, you can build different validators using the specification objects.

Main concern of entities should be keeping track of business state - that's enough of a responsibility and they shouldn't be concerned with validation.

Example

public class User {     public string Id { get; set; }     public string Name { get; set; } } 

Two specifications:

public class IdNotEmptySpecification : ISpecification<User> {     public bool IsSatisfiedBy(User subject)     {         return !string.IsNullOrEmpty(subject.Id);     } }   public class NameNotTakenSpecification : ISpecification<User> {     // omitted code to set service; better use DI     private Service.IUserNameService UserNameService { get; set; }       public bool IsSatisfiedBy(User subject)     {         return UserNameService.NameIsAvailable(subject.Name);     } } 

And a validator:

public class UserPersistenceValidator : IValidator<User> {     private readonly IList<ISpecification<User>> Rules =         new List<ISpecification<User>>             {                 new IdNotEmptySpecification(),                 new NameNotEmptySpecification(),                 new NameNotTakenSpecification()                 // and more ... better use DI to fill this list             };      public bool IsValid(User entity)     {         return BrokenRules(entity).Count() == 0;     }      public IEnumerable<string> BrokenRules(User entity)     {         return Rules.Where(rule => !rule.IsSatisfiedBy(entity))                     .Select(rule => GetMessageForBrokenRule(rule));     }      // ... } 

For completeness, the interfaces:

public interface IValidator<T> {     bool IsValid(T entity);     IEnumerable<string> BrokenRules(T entity); }  public interface ISpecification<T> {     bool IsSatisfiedBy(T subject); } 

Notes

I think Vijay Patel's earlier answer is in the right direction, but I feel it's a bit off. He suggests that the user entity depends on the specification, where I belief that this should be the other way around. This way, you can let the specification depend on services, repositories and context in general, without making your entity depend on them through a specification dependency.

References

A related question with a good answer with example: Validation in a Domain Driven Design.

Eric Evans describes the use of the specification pattern for validation, selection and object construction in chapter 9, pp 145.

This article on the specification pattern with an application in .Net might be of interest to you.

like image 59
12 revs, 3 users 99% Avatar answered Sep 21 '22 20:09

12 revs, 3 users 99%