Where to put global rules validation in DDD

后端 未结 9 2061
一整个雨季
一整个雨季 2020-12-07 07:10

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

相关标签:
9条回答
  • 2020-12-07 08:04

    I would use a Specification to encapsulate the rule. You can then call when the UserName property is updated (or from anywhere else that might need it):

    public class UniqueUserNameSpecification : ISpecification
    {
      public bool IsSatisifiedBy(User user)
      {
         // Check if the username is unique here
      }
    }
    
    public class User
    {
       string _Name;
       UniqueUserNameSpecification _UniqueUserNameSpecification;  // You decide how this is injected 
    
       public string Name
       {
          get { return _Name; }
          set
          {
            if (_UniqueUserNameSpecification.IsSatisifiedBy(this))
            {
               _Name = value;
            }
            else
            {
               // Execute your custom warning here
            }
          }
       }
    }
    

    It won't matter if another developer tries to modify User.Name directly, because the rule will always execute.

    Find out more here

    0 讨论(0)
  • 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.

    0 讨论(0)
  • 2020-12-07 08:08

    I’m not an expert on DDD but I have asked myself the same questions and this is what I came up with: Validation logic should normally go into the constructor/factory and setters. This way you guarantee that you always have valid domain objects. But if the validation involves database queries that impact your performance, an efficient implementation requires a different design.

    (1) Injecting Entities: Injecting entities can be technical difficult and also makes managing application performance very hard due to the fragmentation of you database logic. Seemingly simple operations can now have an unexpectedly performance impact. It also makes it impossible to optimize your domain object for operations on groups of the same kind of entities, you no longer can write a single group query, and instead you always have individual queries for each entity.

    (2) Injecting repository: You should not put any business logic in repositories. Keep repositories simple and focused. They should act as if they were collections and only contain logic for adding, removing and finding objects (some even spinoff the find methods to other objects).

    (3) Domain service This seems the most logical place to handle the validation that requires database querying. A good implementation would make the constructor/factory and setters involved package private, so that the entities can only be created / modified with the domain service.

    0 讨论(0)
提交回复
热议问题