我应该从Domain层抽象验证框架吗?

Vin*_*ves 19 c# architecture domain-driven-design repository fluentvalidation

我正在使用FluentValidation来验证我的服务操作.我的代码看起来像:

using FluentValidation;

IUserService
{
    void Add(User user);
}

UserService : IUserService
{
    public void Add(User user)
    {
       new UserValidator().ValidateAndThrow(user);
       userRepository.Save(user);
    }
} 
Run Code Online (Sandbox Code Playgroud)

UserValidator实现FluentValidation.AbstractValidator.

DDD表示域层必须与技术无关.

我正在做的是使用验证框架而不是自定义异常.

将验证框架放在域层中是一个坏主意吗?

pla*_*alx 41

就像存储库抽象一样?

好吧,我发现你的设计存在一些问题,即使你通过声明一个IUserValidator界面来保护你的域名不受框架影响.

起初,似乎这会导致与存储库和其他基础设施问题相同的抽象策略,但我认为存在巨大差异.

在使用时repository.save(...),您实际上并不关心从域角度来看所有的实现,因为如何持久化事物不是域关注的问题.

但是,不变执行是一个领域关注点,你不应该深入挖掘基础设施细节(UserValidtor现在可以看作是这样),看看它们包含什么,这基本上是你最终要做的事情,如果你从那条路走下去规则将以框架术语表达,并且将存在于域外.

为什么要住在外面?

domain -> IUserRepository
infrastructure -> HibernateUserRepository

domain -> IUserValidator
infrastructure -> FluentUserValidator
Run Code Online (Sandbox Code Playgroud)

始终有效的实体

也许你的设计存在一个更基本的问题,如果你坚持这个学校,你甚至不会问这个问题:永远有效的实体.

从这个角度来看,不变执行是域实体本身的责任,因此甚至不能在没有效的情况下存在.因此,不变规则简单地表示为契约,并且当违反这些规则时抛出异常.

这背后的原因是很多错误都来自于物体处于他们本不应该存在的状态.为了揭示我从Greg Young那里读到的一个例子:

我们的建议,我们现在有一个SendUserCreationEmailService,需要一个 UserProfile......我们怎么能在服务,合理化Name是不是null?我们再检查一下吗?或者更可能......你只是不费心去检查和"希望最好",你希望有人在发送给你之​​前打扰验证它.当然使用TDD我们应该编写的第一个测试之一是,如果我发送一个null名称应该引发错误的客户.但是,一旦我们开始一遍又一遍地编写这些类型的测试,我们就会意识到......"如果我们从不允许名称变为空,我们将不会进行所有这些测试" - Greg Young在http://jeffreypalermo.com上发表评论/博客/的,谬误的最总是有效的实体/

现在不要误解我,显然你不能以这种方式强制执行所有验证规则,因为某些规则特定于禁止该方法的某些业务操作(例如保存实体的草稿副本),但这些规则不被查看与不变执行相同的方式,这是在每个方案中应用的规则(例如,客户必须具有名称).

将始终有效的原则应用于您的代码

如果我们现在查看您的代码并尝试应用始终有效的方法,我们清楚地看到该UserValidator对象没有它的位置.

UserService : IUserService
{
    public void Add(User user)
    {
       //We couldn't even make it that far with an invalid User
       new UserValidator().ValidateAndThrow(user);
       userRepository.Save(user);
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,此时域中没有FluentValidation的位置.如果您仍然不相信,请问自己如何整合价值对象?你是否会在每次实例化时UsernameValidator验证一个Username值对象?显然,这没有任何意义,使用价值对象很难与非常有效的方法相结合.

当抛出异常时,我们如何报告所有错误?

这实际上是我努力的事情,我一直在问自己一段时间(而且我仍然不完全相信我会说些什么).

基本上,我所理解的是,收集和返回错误不是域的工作,这是UI关注点.如果无效数据使其达到域名,那么它只会引发您的注意.

因此,像FluentValidation这样的框架将在UI中找到它们的自然之家,并且将验证视图模型而不是域实体.

我知道,似乎很难接受会有一定程度的重复,但这主要是因为你可能是像我一样处理UI和域的全栈开发人员,而事实上那些可以而且应该被查看作为完全不同的项目.此外,就像视图模型和域模型一样,视图模型验证和域验证可能类似,但用途不同.

此外,如果你仍然担心干嘛,有人曾告诉我代码重用也是"耦合",我认为这个事实在这里特别重要.

处理域中的延迟验证

我不会在这里重新解释这些,但有各种方法来处理域中的延迟验证,例如Ward Cunningham在其Checks模式语言中描述的规范模式和延迟验证方法.如果您拥有Vaughn Vernon的"实施域驱动设计"一书,您还可以阅读第208-215页.

这总是一个权衡取舍的问题

验证是一个非常困难的主题,证据是,截至今天,人们仍然不同意如何做.有很多因素,但最终你想要的是一个实用,可维护和富有表现力的解决方案.您不能总是纯粹主义者,并且必须接受一些规则将被破坏的事实(例如,您可能必须在实体中泄漏一些不显眼的持久性细节以便使用您选择的ORM).

因此,如果您认为您可以忍受一些FluentValidation详细信息进入您的域并且更实用的事实,那么从长远来看,我无法确定它是否会带来更多弊大于利.不会.

  • @Tenek这不是规则本身的提取,这是一个麻烦(在某些情况下甚至可能是可取的),但域对第三方库的依赖性大多数情况下弊大于利.当您必须收集并呈现许多错误时,验证框架最有用,因为目标是保护数据的完整性而不是引导用户完成整个过程并解释出了什么问题. (4认同)
  • 令人敬畏,精彩,美丽的答案@plalx!请看下面的回复! (2认同)