Domain Validation in a CQRS architecture

前端 未结 11 1515
南旧
南旧 2020-12-12 11:25

Danger ... Danger Dr. Smith... Philosophical post ahead

The purpose of this post is to determine if placing the validation logic outside of my domain entiti

11条回答
  •  执念已碎
    2020-12-12 11:43

    I cannot say what I did is the perfect thing to do for I am still struggling with this problem myself and fighting one fight at a time. But I have been doing so far the following thing :

    I have basic classes for encapsulating validation :

    public interface ISpecification where TEntity : class, IAggregate
        {
            bool IsSatisfiedBy(TEntity entity);
        }
    
    internal class AndSpecification : ISpecification where TEntity: class, IAggregate
        {
            private ISpecification Spec1;
            private ISpecification Spec2;
    
            internal AndSpecification(ISpecification s1, ISpecification s2)
            {
                Spec1 = s1;
                Spec2 = s2;
            }
    
            public bool IsSatisfiedBy(TEntity candidate)
            {
                return Spec1.IsSatisfiedBy(candidate) && Spec2.IsSatisfiedBy(candidate);
            }
    
    
        }
    
        internal class OrSpecification : ISpecification where TEntity : class, IAggregate
        {
            private ISpecification Spec1;
            private ISpecification Spec2;
    
            internal OrSpecification(ISpecification s1, ISpecification s2)
            {
                Spec1 = s1;
                Spec2 = s2;
            }
    
            public bool IsSatisfiedBy(TEntity candidate)
            {
                return Spec1.IsSatisfiedBy(candidate) || Spec2.IsSatisfiedBy(candidate);
            }
        }
    
        internal class NotSpecification : ISpecification where TEntity : class, IAggregate
        {
            private ISpecification Wrapped;
    
            internal NotSpecification(ISpecification x)
            {
                Wrapped = x;
            }
    
            public bool IsSatisfiedBy(TEntity candidate)
            {
                return !Wrapped.IsSatisfiedBy(candidate);
            }
        }
    
        public static class SpecsExtensionMethods
        {
            public static ISpecification And(this ISpecification s1, ISpecification s2) where TEntity : class, IAggregate
            {
                return new AndSpecification(s1, s2);
            }
    
            public static ISpecification Or(this ISpecification s1, ISpecification s2) where TEntity : class, IAggregate
            {
                return new OrSpecification(s1, s2);
            }
    
            public static ISpecification Not(this ISpecification s) where TEntity : class, IAggregate
            {
                return new NotSpecification(s);
            }
        }
    

    and to use it, I do the following :

    command handler :

     public class MyCommandHandler :  CommandHandler
    {
      public override CommandValidation Execute(MyCommand cmd)
            {
                Contract.Requires(cmd != null);
    
               var existingAR= Repository.GetById(cmd.Id);
    
                if (existingIntervento.IsNull())
                    throw new HandlerForDomainEventNotFoundException();
    
                existingIntervento.DoStuff(cmd.Id
                                    , cmd.Date
                                    ...
                                    );
    
    
                Repository.Save(existingIntervento, cmd.GetCommitId());
    
                return existingIntervento.CommandValidationMessages;
            }
    

    the aggregate :

     public void DoStuff(Guid id, DateTime dateX,DateTime start, DateTime end, ...)
            {
                var is_date_valid = new Is_dateX_valid(dateX);
                var has_start_date_greater_than_end_date = new Has_start_date_greater_than_end_date(start, end);
    
            ISpecification specs = is_date_valid .And(has_start_date_greater_than_end_date );
    
            if (specs.IsSatisfiedBy(this))
            {
                var evt = new AgregateStuffed()
                {
                    Id = id
                    , DateX = dateX
    
                    , End = end        
                    , Start = start
                    , ...
                };
                RaiseEvent(evt);
            }
        }
    

    the specification is now embedded in these two classes :

    public class Is_dateX_valid : ISpecification
        {
            private readonly DateTime _dateX;
    
            public Is_data_consuntivazione_valid(DateTime dateX)
            {
                Contract.Requires(dateX== DateTime.MinValue);
    
                _dateX= dateX;
            }
    
            public bool IsSatisfiedBy(MyAggregate i)
            {
                if (_dateX> DateTime.Now)
                {
                    i.CommandValidationMessages.Add(new ValidationMessage("datex greater than now"));
                    return false;
                }
    
                return true;
            }
        }
    
        public class Has_start_date_greater_than_end_date : ISpecification
        {
            private readonly DateTime _start;
            private readonly DateTime _end;
    
            public Has_start_date_greater_than_end_date(DateTime start, DateTime end)
            {
                Contract.Requires(start == DateTime.MinValue);
                Contract.Requires(start == DateTime.MinValue);
    
                _start = start;
                _end = end;
            }
    
            public bool IsSatisfiedBy(MyAggregate i)
            {
                if (_start > _end)
                {
                    i.CommandValidationMessages.Add(new ValidationMessage(start date greater then end date"));
                    return false;
                }
    
                return true;
            }
        }
    

    This allows me to reuse some validations for different aggregate and it is easy to test. If you see any flows in it. I would be real happy to discuss it.

    yours,

提交回复
热议问题