域验证在CQRS架构(Domain Validation in a CQRS architectur

2019-06-25 03:38发布

危险......危险史密斯博士...哲学领先后

这篇文章的目的是,以确定是否将验证逻辑超出了我的域实体(实际上聚合根)实际上是给予我更多的灵活性,或者它的敢死队代码

基本上我想知道是否有更好的方法来验证我的域实体。 这是我正在计划做,但我想你的意见

我认为第一种方法是:

class Customer : EntityBase<Customer>
{
   public void ChangeEmail(string email)
   {
      if(string.IsNullOrWhitespace(email))   throw new DomainException(“...”);
      if(!email.IsEmail())  throw new DomainException();
      if(email.Contains(“@mailinator.com”))  throw new DomainException();
   }
}

我居然因为即使当我在正确的实体封装验证逻辑不喜欢这样的验证,这违反了打开/关闭原则(对扩展开放,但关闭了修改),我已经发现违反这一原则,代码维护变得当应用程序在复杂性的增加了一个真正的痛苦。 为什么? 由于域名规则的改变往往比我们愿意承认,如果规则是隐性的,嵌入在像这样的实体,它们是难以测试,难以阅读,难以维持,但真正的原因,我不喜欢这个做法是:如果验证规则的改变,我要来和编辑我的域实体。 这是一个非常简单的例子,但在RL验证可能更加复杂

所以以下乌迪大汉的理念, 使得角色的明确 ,以及埃里克·埃文斯在蓝皮书建议,接下来的尝试是实现该规范的模式,这样的事情

class EmailDomainIsAllowedSpecification : IDomainSpecification<Customer>
{
   private INotAllowedEmailDomainsResolver invalidEmailDomainsResolver;
   public bool IsSatisfiedBy(Customer customer)
   {
      return !this.invalidEmailDomainsResolver.GetInvalidEmailDomains().Contains(customer.Email);
   }
}

但后来我意识到,为了遵循这个方法,我只好先变异我的实体,以传递值被valdiated,在这种情况下,电子邮件,但它们发生突变会导致我的域事件中被解雇,我不希望发生,直到新的电子邮件是有效的

因此,考虑这些方法后,我来到了这一个,因为我要实现CQRS架构:

class EmailDomainIsAllowedValidator : IDomainInvariantValidator<Customer, ChangeEmailCommand>
{
   public void IsValid(Customer entity, ChangeEmailCommand command)
   {
      if(!command.Email.HasValidDomain())  throw new DomainException(“...”);
   }
}

嗯,这是主要的想法,实体传递到验证的情况下,我们需要从实体一定的价值来进行验证,命令中包含来自用户的数据,并且由于验证被认为是可注射的对象,他们可能有外部依赖注入如果验证需要它。

现在的窘境 ,我很高兴有这样的设计,因为我的验证被封装在单个对象带来很多优点:易于单元测试,易于维护,域名不变量所使用的通用语言,易于扩展,验证逻辑是明确表示集中和验证可以一起使用来执行复杂的域规则。 甚至当我知道我把我的实体验证他们之外(你可以说一个代码味道 - 贫血域),但我认为折衷是可以接受的

但我还没有想出如何实现它在一个干净的方式一两件事。 我应该如何使用这个组件...

因为它们将被注入,它们自然不会适合我的域实体里面,所以基本上我看到两个选项:

  1. 通过验证程序,以我的实体的每个方法

  2. 验证我的对象外(从命令处理程序)

我不愉快的选项1,所以我会解释我是如何与选项2做

class ChangeEmailCommandHandler : ICommandHandler<ChangeEmailCommand>
{
   // here I would get the validators required for this command injected
   private IEnumerable<IDomainInvariantValidator> validators;
   public void Execute(ChangeEmailCommand command)
   {
      using (var t = this.unitOfWork.BeginTransaction())
      {
         var customer = this.unitOfWork.Get<Customer>(command.CustomerId);
         // here I would validate them, something like this
         this.validators.ForEach(x =. x.IsValid(customer, command));
         // here I know the command is valid
         // the call to ChangeEmail will fire domain events as needed
         customer.ChangeEmail(command.Email);
         t.Commit();
      }
   }
}

嗯,这是它。 你能给我你的想法这样或共享域实体验证您的经验

编辑

我想是不是从清楚我的问题,但真正的问题是:隐藏域规则在应用程序的未来可维护性严重影响,也域规则的应用程序的生命周期中经常改变。 因此,本实施他们的脑海里,让我们轻松地扩展他们。 现在想象一下,在未来的一个规则引擎实现,如果规则被封装在域实体之外,这种变化会更容易实现

据我所知,将验证我的实体之外打破了封装在他的回答中提到@jgauffin,但我认为这将验证在单个对象的好处不只是保持一个实体的封装更为可观。 现在我觉得封装更有意义在传统的n层体系结构,因为实体是在领域层的几个地方使用,但在CQRS架构,当命令到达时,会有一个命令处理程序访问的聚合根和对聚合根进行操作只有建立一个完善的窗口放置验证。

我想提出的优点之间的比较小的地方验证实体内VS将其放置在单独的对象

  • 验证在个别对象

    • 临。 易写
    • 临。 易于测试
    • 临。 它明确表示
    • 临。 它成为域设计的一部分,对现行通用语言
    • 临。 因为它是现在设计的一部分,它可以使用UML图建模
    • 临。 非常易于维护
    • 临。 让我的实体和验证逻辑松耦合
    • 临。 易于扩展
    • 临。 继SRP
    • 临。 继开/关原则
    • 临。 不打破迪米特(MMM)的法律?
    • 临。 I'is集中
    • 临。 它可以被重用
    • 临。 如果需要的话,外部依赖,可以很容易地注入
    • 临。 如果使用插件模型,新的验证可以仅通过丢弃新的组件,而无需重新编译整个应用程序被添加
    • 临。 实现一个规则引擎会更容易
    • CON。 打破封装
    • CON。 如果封装是强制性的,我们将不得不单独验证传递给实体(聚集)方法
  • 验证封装在实体内

    • 临。 封装?
    • 临。 可重复使用的?

我喜欢读你的这个想法

Answer 1:

我同意许多其他答复提出的概念,但我把它们放在一起在我的代码。

首先,我同意使用值对象为包括行为的值是常见的封装业务规则和一个电子邮件地址是一个完美的候选人的好方法。 不过,我倾向于这种限制是恒定的,并且不会频繁改变规则。 我敢肯定,你正在寻找一个更通用的方法和电子邮件仅仅是一个例子,所以我不会集中在一个用例。

我的方法的关键是认识到在验证应用程序中的不同位置提供不同的目的。 简单地说,只需要什么,以确保当前的操作可以不意外/意想不到的结果执行验证。 这导致的问题,验证应该发生什么地方?

在你的榜样,我会问自己,如果域实体真正关心的是,电子邮件地址符合某种模式和其他规则还是我们根本不在乎,当ChangeEmail被称为“邮件”不能为空或空白? 如果是后者,不是简单的检查,以确保一个值存在是所有需要在ChangeEmail方法。

在CQRS,即修改应用程序的状态都发生变化,与在命令处理程序执行命令(如你所示)。 我通常会放置任何“钩子”到业务规则等,其验证该操作可在命令处理程序执行。 其实,我按照你的注入验证到命令处理程序,它允许我向/替换规则集无需改变处理程序的方法。 这些“动态”的规则允许我来定义业务规则,比如什么构成一个有效的E-mail地址,我才改变实体的状态 - 进一步确保它不会进入无效状态。 但在这种情况下,“无效”是由业务逻辑和定义,正如你所指出,是高度volitile。

说完拿出通过里昂证券的行列,我发现这个变化很难采用,因为它似乎破坏了封装。 但是,我agrue如果你退后一步,问什么样的角色验证真正服务的模式,封装不破。

我发现这些细微之处是在保持我的头脑清醒,对这个问题非常重要。 有验证,以防止坏的数据(例如缺少论据,空值,空字符串,等等),在方法本身属于并有验证,以确保业务规则得到执行。 在前者的情况下,如果客户必须拥有一个电子邮件地址,那么我唯一需要的规则予以关注,以防止我的域对象变得无效,是保证电子邮件地址已经提供给ChangeEmail方法。 其他规则是关于价值本身的有效性更高级别的关注,并真的在域实体本身的有效性没有影响。

这已经有很多与其他开发商,但是当大多数采取了更广阔的视野和查处的作用真的验证服务,他们往往见光“讨论”的来源。

最后,也是UI验证的地方(和UI我的意思是无论作为接口的应用是它的屏幕,服务端点或其他)。 我觉得完全有理由复制一些逻辑的UI来为用户提供更好的交互性。 但正是因为这个验证服务于某个单一的目的,为什么我让这样的重复。 然而,使用注射验证器/规范对象促进以这种方式重新使用,而不必在多个地点定义这些规则的负面影响。

不知道有没有什么帮助或不...



Answer 2:

我不会建议trowing的代码大块到你的域进行验证。 我们看到他们为我们的领域缺少概念的异味消除了大部分我们的尴尬放置验证的。 在你的示例代码你写我看到验证一个电子邮件地址。 客户不具备任何与电子邮件验证。

为什么不做出ValueObject称为Email ,在构造做这个验证?

我的经验是尴尬放置验证提示都可以在您的域名错过概念。 你能赶上他们在验证对象,但我更喜欢的值对象,因为你让你的域的相关概念的一部分。



Answer 3:

你把验证错了地方。

您应该使用ValueObjects这样的事情。 观看此演示http://www.infoq.com/presentations/Value-Objects-Dan-Bergh-Johnsson它也将教你有关数据为重心。

此外,还对如何使用静态的验证方法的ala Email.IsValid重用数据验证,如例如一个样品(字符串)



Answer 4:

我在一个项目的开始,我要实现我的域实体超出了我的验证。 我的域实体将包含逻辑,以保护任何不变量(如缺少论据,空值,空字符串,集合等)。 但实际的业务规则将生活在验证器类。 我@SonOfPirate的心态...

我使用FluentValidation将基本上给我一堆我的域实体采取行动验证器:又名,规范模式。 此外,按照Eric的蓝皮书所描述的模式,我可以构造具有他们可能需要进行验证的任何数据的验证(从数据库或其它储存库或服务吧)。 我还必须在这里也注入任何依赖关系的选项。 我也可以撰写和重用这些验证程序(如地址验证器可以同时在一个雇员的验证和公司验证可重复使用)。 我有一个验证的工厂充当“服务定位器”:

public class ParticipantService : IParticipantService
{
    public void Save(Participant participant)
    {
        IValidator<Participant> validator = _validatorFactory.GetValidator<Participant>();
        var results = validator.Validate(participant);
            //if the participant is valid, register the participant with the unit of work
            if (results.IsValid)
            {
                if (participant.IsNew)
                {
                    _unitOfWork.RegisterNew<Participant>(participant);
                }
                else if (participant.HasChanged)
                {
                    _unitOfWork.RegisterDirty<Participant>(participant);
                }
            }
            else
            {
                _unitOfWork.RollBack();
                //do some thing here to indicate the errors:generate an exception (or fault) that contains the validation errors. Or return the results
            }
    }

}

和验证将包含的代码,这样的:

   public class ParticipantValidator : AbstractValidator<Participant>
    {
        public ParticipantValidator(DateTime today, int ageLimit, List<string> validCompanyCodes, /*any other stuff you need*/)
        {...}

    public void BuildRules()
    {
             RuleFor(participant => participant.DateOfBirth)
                    .NotNull()
                    .LessThan(m_today.AddYears(m_ageLimit*-1))
                    .WithMessage(string.Format("Participant must be older than {0} years of age.", m_ageLimit));

            RuleFor(participant => participant.Address)
                .NotNull()
                .SetValidator(new AddressValidator());

            RuleFor(participant => participant.Email)
                .NotEmpty()
                .EmailAddress();
            ...
}

    }

我们必须支持多种类型的演示:通过服务网站,WinForms和批量加载数据。 在牵制所有这些是一组在一个单一的,一致的方式揭露系统的功能服务的。 我们不使用实体框架或ORM的,我不会来烦你的理由。

这是为什么我喜欢这种方法:

  • 包含在验证程序的业务规则完全单元测试。
  • 我可以从简单的规则组成更复杂的规则
  • 我可以使用多个位置的验证在我的系统(我们支持网站和WinForms,并公开功能的服务),所以如果有一个用例中,从网站不同的服务需要,那么一个稍微不同的规则我可以搞定。
  • 所有vaildation在一个位置表达,我可以选择如何/在哪里注入和撰写这一点。


Answer 5:

我不会把它从继承的类EntityBase我的域模型,因为它的夫妇到您的持久层。 但那只是我的个人意见。

我不会从移动电子邮件验证逻辑Customer到别的跟随开/关原则。 对我来说,下面的开启/关闭将意味着你有以下层次:

public class User
{
    // some basic validation
    public virtual void ChangeEmail(string email);
}

public class Employee : User
{
    // validates internal email
    public override void ChangeEmail(string email);
}

public class Customer : User
{
    // validate external email addresses.
    public override void ChangeEmail(string email);
}

建议您从移动领域模型控制到任意类,因此打破了封装。 我宁愿我的重构类( Customer )遵守比做了新的业务规则。

使用域事件触发系统的其它部分,以获得更松散耦合的架构,但不要用命令/事件违反了封装。

例外

我只注意到你扔DomainException 。 这是一个方式一般异常。 你为什么不使用参数异常或FormatException ? 他们所描述的错误好得多。 而且不要忘了包括上下文信息,帮助您避免在未来的例外。

更新

配售逻辑的类外是自找麻烦恕我直言。 你如何控制其有效性规则使用? 代码的一部分可以使用SomeVeryOldRule而另一个使用验证时NewAndVeryStrictRule 。 它可能不是故意的,但它可以在代码库的增长,并会发生。

这听起来像你已经决定无视基本面OOP(封装)之一。 继续使用通用/外部验证框架,但不要说我没提醒你;)

UPDATE2

感谢您的耐心和答案,这就是为什么我张贴了这个问题的原因,我觉得同样的实体应该负责,以保证它在有效的状态(我已经在以前的项目中做到了),但把它的好处在单个对象是巨大的,就像我张贴甚至还有使用单个对象,并保持封装的方式,但我个人是不太满意的设计,但在另一方面,它是不出来的表,认为这ChangeEmail(IEnumerable的>验证,字符串email)我还没有详细想到imple。 虽然

这允许程序员指定任何规则,它可能会或可能不会是当前正确的业务规则。 开发者可以只写

customer.ChangeEmail(new IValidator<Customer>[] { new NonValidatingRule<Customer>() }, "notAnEmail")

它接受一切。 而这些规则在每一个地方到指定ChangeEmail被调用。

如果你想使用一个规则引擎,创建一个单代理:

public class Validator
{
    IValidatorEngine _engine;

    public static void Assign(IValidatorEngine engine)
    {
        _engine = engine;
    }

    public static IValidatorEngine Current { get { return _engine; } }
}

..并使用它从像域模型方法中

public class Customer
{
    public void ChangeEmail(string email)
    {
        var rules = Validator.GetRulesFor<Customer>("ChangeEmail");
        rules.Validate(email);

        // valid
    }

}

与解决方案的问题是,它会成为一个维护的噩梦,因为规则的依赖是隐藏的。 除非你测试每个域模型方法和每个方法的每个规则下,如果所有的规则已经规定和工作,你永远无法知道。

解决的办法是更灵活,但恕我直言,将需要更多的时间来实现,而不是重构谁的业务规则得到了改变的方法。



Answer 6:

我不能说什么,我所做的是对我仍然有这个问题我自己挣扎和战斗在同一时间一个拼做完美的东西。 但是,我一直在做,到目前为止以下的事情:

我有封装验证基本类:

public interface ISpecification<TEntity> where TEntity : class, IAggregate
    {
        bool IsSatisfiedBy(TEntity entity);
    }

internal class AndSpecification<TEntity> : ISpecification<TEntity> where TEntity: class, IAggregate
    {
        private ISpecification<TEntity> Spec1;
        private ISpecification<TEntity> Spec2;

        internal AndSpecification(ISpecification<TEntity> s1, ISpecification<TEntity> s2)
        {
            Spec1 = s1;
            Spec2 = s2;
        }

        public bool IsSatisfiedBy(TEntity candidate)
        {
            return Spec1.IsSatisfiedBy(candidate) && Spec2.IsSatisfiedBy(candidate);
        }


    }

    internal class OrSpecification<TEntity> : ISpecification<TEntity> where TEntity : class, IAggregate
    {
        private ISpecification<TEntity> Spec1;
        private ISpecification<TEntity> Spec2;

        internal OrSpecification(ISpecification<TEntity> s1, ISpecification<TEntity> s2)
        {
            Spec1 = s1;
            Spec2 = s2;
        }

        public bool IsSatisfiedBy(TEntity candidate)
        {
            return Spec1.IsSatisfiedBy(candidate) || Spec2.IsSatisfiedBy(candidate);
        }
    }

    internal class NotSpecification<TEntity> : ISpecification<TEntity> where TEntity : class, IAggregate
    {
        private ISpecification<TEntity> Wrapped;

        internal NotSpecification(ISpecification<TEntity> x)
        {
            Wrapped = x;
        }

        public bool IsSatisfiedBy(TEntity candidate)
        {
            return !Wrapped.IsSatisfiedBy(candidate);
        }
    }

    public static class SpecsExtensionMethods
    {
        public static ISpecification<TEntity> And<TEntity>(this ISpecification<TEntity> s1, ISpecification<TEntity> s2) where TEntity : class, IAggregate
        {
            return new AndSpecification<TEntity>(s1, s2);
        }

        public static ISpecification<TEntity> Or<TEntity>(this ISpecification<TEntity> s1, ISpecification<TEntity> s2) where TEntity : class, IAggregate
        {
            return new OrSpecification<TEntity>(s1, s2);
        }

        public static ISpecification<TEntity> Not<TEntity>(this ISpecification<TEntity> s) where TEntity : class, IAggregate
        {
            return new NotSpecification<TEntity>(s);
        }
    }

并使用它,我做到以下几点:

命令处理程序:

 public class MyCommandHandler :  CommandHandler<MyCommand>
{
  public override CommandValidation Execute(MyCommand cmd)
        {
            Contract.Requires<ArgumentNullException>(cmd != null);

           var existingAR= Repository.GetById<MyAggregate>(cmd.Id);

            if (existingIntervento.IsNull())
                throw new HandlerForDomainEventNotFoundException();

            existingIntervento.DoStuff(cmd.Id
                                , cmd.Date
                                ...
                                );


            Repository.Save(existingIntervento, cmd.GetCommitId());

            return existingIntervento.CommandValidationMessages;
        }

合计:

 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<MyAggregate> 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);
        }
    }

该规范现在嵌入在这两个类:

public class Is_dateX_valid : ISpecification<MyAggregate>
    {
        private readonly DateTime _dateX;

        public Is_data_consuntivazione_valid(DateTime dateX)
        {
            Contract.Requires<ArgumentNullException>(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<MyAggregate>
    {
        private readonly DateTime _start;
        private readonly DateTime _end;

        public Has_start_date_greater_than_end_date(DateTime start, DateTime end)
        {
            Contract.Requires<ArgumentNullException>(start == DateTime.MinValue);
            Contract.Requires<ArgumentNullException>(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;
        }
    }

这让我重新用于不同的骨料一些验证,很容易进行测试。 如果你看到它的任何流量。 我会很高兴真正讨论此事。

你的,



Answer 7:

从我的经验OO(我不是一个专家DDD)从实体到一个更高的抽象水平(成命令处理程序)移动你的代码会导致代码重复。 这是因为每次命令处理程序得到一个电子邮件地址,它必须实例化电子邮件验证规则。 一段时间后,这种代码就会腐烂,而且会闻到非常糟糕。 在当前的例子也可能不,如果你没有这改变了电子邮件地址,另一个命令这样做,但在其他情况下,它肯定会...

如果你不希望移动规则回到较低的抽象层次,像实体或电子邮件值对象,那么我强烈建议你通过分组规则,以减轻疼痛。 因此,在你的电子邮件例如,下面的3个规则:

  if(string.IsNullOrWhitespace(email))   throw new DomainException(“...”);
  if(!email.IsEmail())  throw new DomainException();
  if(email.Contains(“@mailinator.com”))  throw new DomainException();

可以是部分EmailValidationRule ,你可以重复使用更容易组。

从我的角度来看,没有明确答案的问题放在哪里验证逻辑。 它可以根据抽象层的每一个对象的一部分。 在你目前的情况下,电子邮件地址的正式检查可以是部分EmailValueObjectmailinator规则可以在你的国家,你的用户就不能拥有指向该域上的电子邮件地址的更高的抽象水平概念的一部分。 因此,举例来说,如果有人想与您的用户联系不注册,那么你就可以检查她的电子邮件对正式的验证,但你没有检查她的电子邮件对mailinator规则。 等等...

所以我完全谁声称,这种尴尬的放置验证的是一个不好的设计的征兆@pjvds同意。 我不认为你会打破封装的任何收益,但它是你的选择,这将是你的痛苦。



Answer 8:

在您的示例验证是一个值对象,而不是一个实体(或聚合根)的验证。

我想验证分成不同的区域。

  1. 验证的内部特性Email值对象在内部。

我坚持一个聚合不应该是处于无效状态的规则。 扩展该委托人值对象,其中实际的。

使用createNew()实例从用户输入的电子邮件。 这迫使它根据你目前的规则(以下简称“user@email.com”的格式,例如)是有效的。

使用createExisting()来实例化从持久存储的电子邮件。 这不执行验证,这是很重要的 - 你不想异常被抛出一个存储的电子邮件,这是有效的,但昨天今天无效。

class Email
{
    private String value_;

    // Error codes
    const Error E_LENGTH = "An email address must be at least 3 characters long.";
    const Error E_FORMAT = "An email address must be in the 'user@email.com' format.";

    // Private constructor, forcing the use of factory functions
    private Email(String value)
    {
        this.value_ = value;
    }

    // Factory functions
    static public Email createNew(String value)
    {
        validateLength(value, E_LENGTH);
        validateFormat(value, E_FORMAT);
    }

    static public Email createExisting(String value)
    {
        return new Email(value);
    }

    // Static validation methods
    static public void validateLength(String value, Error error = E_LENGTH)
    {
        if (value.length() < 3)
        {
            throw new DomainException(error);
        }
    }

    static public void validateFormat(String value, Error error = E_FORMAT)
    {
        if (/* regular expression fails */)
        {
            throw new DomainException(error);
        }
    }

}
  1. 验证的“外部”特性Email值对象外部,例如,在一个服务。

     class EmailDnsValidator implements IEmailValidator { const E_MX_MISSING = "The domain of your email address does not have an MX record."; private DnsProvider dnsProvider_; EmailDnsValidator(DnsProvider dnsProvider) { dnsProvider_ = dnsProvider; } public void validate(String value, Error error = E_MX_MISSING) { if (!dnsProvider_.hasMxRecord(/* domain part of email address */)) { throw new DomainException(error); } } } class EmailDomainBlacklistValidator implements IEmailValidator { const Error E_DOMAIN_FORBIDDEN = "The domain of your email address is blacklisted."; public void validate(String value, Error error = E_DOMAIN_FORBIDDEN) { if (/* domain of value is on the blacklist */)) { throw new DomainException(error); } } } 

好处:

  • 所述的使用createNew()createExisting()工厂函数允许在内部验证控制。

  • 有可能某些验证程序的“退出”,例如,跳过长度检查,直接使用的验证方法。

  • 也可以对外部验证(DNS MX记录和域黑名单)的“退出”。 例如,一个项目,我最初曾在验证的MX记录的所有脑干的域名,但最终删除,因为使用“动态IP”解决方案类型的客户数量这一点。

  • 它很容易地查询你的不适合当前的验证规则的电子邮件地址持久性存储,但运行一个简单的查询和处理每封电子邮件的“新”,而不是“现有” - 如果一个异常被抛出,有一个问题。 从那里,你可以发出,例如FlagCustomerAsHavingABadEmail命令,使用异常错误消息作为用户指导,当他们看到的消息。

  • 允许程序员提供的错误代码提供了灵活性。 例如,在发送时UpdateEmailAddress命令的错误“您的电子邮件地址必须至少为3个字符长”是自我解释。 但是,更新多个电子邮件地址(家庭和工作)时,上面的错误消息不表明哪些邮件是错误的。 供给错误代码/消息允许您提供给最终用户更丰富的反馈。



Answer 9:

我写了一篇博客文章关于这个主题而回。 该职位的前提是有不同类型的验证。 我打电话给他们浅表验证和基于域命令验证。

这个简单的版本是这样的。 验证之类的东西“是一个数字”或“电子邮件地址”的往往不只是肤浅的。 能做到这些之前,命令到达域实体。

然而,在验证更依赖于域那么它是正确的地方是在域。 比如,也许你对重量和货物的类型有一定的货车可以采取一些规则。 这听起来更像域逻辑。

然后你有混合类型。 比如像基于集合的验证。 这些都需要发出或注入到该域的命令之前发生(尽量避免,如果在所有可能的 - 限制的依赖是一件好事)。

无论如何,你可以在这里阅读完整的信息: 如何验证命令在CQRS应用



Answer 10:

我还在这个概念进行实验,但你可以尝试装饰。 如果你使用SimpleInjector您可以轻松地将是冲在最前面您的命令处理程序的自己的验证类。 然后,该命令可以假定它是有效的,如果它得到了那么远。 然而,这意味着所有的验证应在命令来完成,而不是实体。 该实体将不会进入无效状态。 但每个命令都必须实现它自己的验证完全如此相似的命令可能有规则的重复,但你既可以抽象的共同规则,分享或治疗不同的命令当作真正的独立。



Answer 11:

为解释您可以使用域事件基于消息的解决方案在这里 。

例外是不是所有验证错误正确的方法,是不是说,一个无效的实体是一个特例。

如果验证是不平凡的,为了验证聚集的逻辑,可以直接在服务器上执行,而你正在试图建立新的输入,你可以(也就是使用您的域或应用程序)提高一个域事件告诉给用户为什么输入不正确。



文章来源: Domain Validation in a CQRS architecture