代码之家  ›  专栏  ›  技术社区  ›  Todd Smith Brandon

域驱动设计中的验证

  •  57
  • Todd Smith Brandon  · 技术社区  · 16 年前

    如何处理域驱动设计中复杂聚合的验证?您是否整合了业务规则/验证逻辑?

    我理解论证验证。我了解可以附加到模型本身的属性验证,并执行诸如检查电子邮件地址或邮政编码是否有效或名字是否具有最小和最大长度之类的操作。

    但是涉及多个模型的复杂验证呢?您通常将这些规则和方法放在架构中的什么位置?如果有的话,您使用什么模式来实现它们?

    5 回复  |  直到 7 年前
        1
  •  37
  •   user247702    8 年前

    我喜欢吉米·博加德解决这个问题的方法。他在博客上写了一篇题为 "Entity validation with visitors and extension methods" 其中,他提出了一种非常优雅的实体验证方法,建议实现一个单独的类来存储验证代码。

    public interface IValidator<T>
    {
        bool IsValid(T entity);
        IEnumerable<string> BrokenRules(T entity);
    }
    
    public class OrderPersistenceValidator : IValidator<Order>
    {
        public bool IsValid(Order entity)
        {
            return BrokenRules(entity).Count() == 0;
        }
    
        public IEnumerable<string> BrokenRules(Order entity)
        {
            if (entity.Id < 0)
                yield return "Id cannot be less than 0.";
    
            if (string.IsNullOrEmpty(entity.Customer))
                yield return "Must include a customer.";
    
            yield break;
        }
    }
    
        2
  •  55
  •   Ben Scheirman    16 年前

    而不是依赖 IsValid(xx) 在整个申请过程中,请考虑听取Greg Young的建议:

    不要让你的实体进入 无效状态。

    这基本上意味着,您将从将实体视为纯数据容器,而更多地将其视为具有行为的对象。

    以某人的地址为例:

     person.Address = "123 my street";
     person.City = "Houston";
     person.State = "TX";
     person.Zip = 12345;
    

    在这些调用之间,您的实体是无效的(因为您的属性不一致)。现在考虑一下:

    person.ChangeAddress(.......); 
    

    所有与更改地址行为相关的调用现在都是一个原子单元。您的实体在这里永远不会无效。

    如果您采用这种建模行为而不是状态的思想,那么您可以到达不允许无效实体的模型。

    有关这方面的讨论,请查看以下InfoQ访谈: http://www.infoq.com/interviews/greg-young-ddd

        3
  •  6
  •   thinkbeforecoding    16 年前

    我通常使用规范类, 它提供了一种方法(这是C,但您可以用任何语言翻译它):

    bool IsVerifiedBy(TEntity candidate)
    

    此方法对候选对象及其关系执行完整的检查。 您可以使用规范类中的参数使其参数化,例如检查级别…

    您还可以添加一个方法来了解候选人为什么没有验证规范:

    IEnumerable<string> BrokenRules(TEntity canditate) 
    

    您可以简单地决定实现这样的第一个方法:

    bool IsVerifiedBy(TEntity candidate)
    {
      return BrokenRules(candidate).IsEmpty();
    }
    

    对于破坏的规则,我通常编写一个迭代器:

    IEnumerable<string> BrokenRules(TEntity candidate)
    {
      if (someComplexCondition)
          yield return "Message describing cleary what is wrong...";
      if (someOtherCondition) 
          yield return
       string.Format("The amount should not be {0} when the state is {1}",
            amount, state);
    }
    

    对于本地化,您应该使用资源,为什么不将文化传递给brokenrules方法呢? 我将这些类放在模型名称空间中,名称建议使用它们。

        4
  •  0
  •   Todd Skelton    7 年前

    多个模型验证应该通过聚合根进行。如果必须跨聚合根进行验证,则可能存在设计缺陷。

    我对聚合进行验证的方法是返回一个响应接口,它告诉我验证是否通过/失败,以及有关失败原因的任何消息。

    您可以验证聚合根上的所有子模型,使它们保持一致。

    // Command Response class to return from public methods that change your model
    public interface ICommandResponse
    {
        CommandResult Result { get; }
        IEnumerable<string> Messages { get; }
    }
    
    // The result options
    public enum CommandResult
    {
        Success = 0,
        Fail = 1
    }
    
    // My default implementation
    public class CommandResponse : ICommandResponse
    {
        public CommandResponse(CommandResult result)
        {
            Result = result;
        }
    
        public CommandResponse(CommandResult result, params string[] messages) : this(result)
        {
            Messages = messages;
        }
    
        public CommandResponse(CommandResult result, IEnumerable<string> messages) : this(result)
        {
            Messages = messages;
        }
    
        public CommandResult Result { get; private set; }
    
        public IEnumerable<string> Messages { get; private set; }
    }
    
    // usage
    public class SomeAggregateRoot
    {
        public string SomeProperty { get; private set; }
    
    
        public ICommandResponse ChangeSomeProperty(string newProperty)
        {
            if(newProperty == null)
            {
                return new CommandResponse(CommandResult.Fail, "Some property cannot be changed to null");
            }
    
            SomeProperty = newProperty;
    
            return new CommandResponse(CommandResult.Success);
        }
    }
    
        5
  •  -1
  •   Jason Watmore    8 年前

    这个问题现在有点老了,但如果有人感兴趣,这里介绍我如何在我的服务类中实现验证。

    我有个私人的 验证 方法在我的每一个服务类中,它接受一个正在执行的实体实例和操作,如果验证失败,则会引发一个自定义异常,其中包含破坏规则的详细信息。

    带有内置验证的示例DocumentService

    public class DocumentService : IDocumentService
    {
        private IRepository<Document> _documentRepository;
    
        public DocumentService(IRepository<Document> documentRepository)
        {
            _documentRepository = documentRepository;
        }
    
        public void Create(Document document)
        {
            Validate(document, Action.Create);
    
            document.CreatedDate = DateTime.Now;
    
            _documentRepository.Create(document);
        }
    
        public void Update(Document document)
        {
            Validate(document, Action.Update);
    
            _documentRepository.Update(document);
        }
    
        public void Delete(int id)
        {
            Validate(_documentRepository.GetById(id), Action.Delete);
    
            _documentRepository.Delete(id);
        }
    
        public IList<Document> GetAll()
        {
            return _documentRepository
                .GetAll()
                .OrderByDescending(x => x.PublishDate)
                .ToList();
        }
    
        public int GetAllCount()
        {
            return _documentRepository
                .GetAll()
                .Count();
        }
    
        public Document GetById(int id)
        {
            return _documentRepository.GetById(id);
        }
    
        // validation 
    
        private void Validate(Document document, Action action)
        {
            var brokenRules = new List<string>();
    
            if (action == Action.Create || action == Action.Update)
            {
                if (string.IsNullOrWhiteSpace(document.Title))
                    brokenRules.Add("Title is required");
    
                if (document.PublishDate == null)
                    brokenRules.Add("Publish Date is required");
            }
    
            if (brokenRules.Any())
                throw new EntityException(string.Join("\r\n", brokenRules));
        }
    
        private enum Action
        {
            Create,
            Update,
            Delete
        }
    }
    

    我喜欢这种方法,因为它允许我将所有的核心验证逻辑放在一个地方,从而使事情变得简单。