一尘不染

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

c#

我正在使用FluentValidation验证我的服务操作。我的代码如下:

using FluentValidation;

IUserService
{
    void Add(User user);
}

UserService : IUserService
{
    public void Add(User user)
    {
       new UserValidator().ValidateAndThrow(user);
       userRepository.Save(user);
    }
}

UserValidator实现FluentValidation.AbstractValidator。

DDD表示,域层必须独立于技术。

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

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


阅读 241

收藏
2020-05-19

共1个答案

一尘不染

就像存储库抽象一样?

好吧,即使您通过声明一个IUserValidator接口将您的域与框架隔离开,我也看到了您的设计中的一些问题。

最初,这是否会导致与存储库和其他基础架构问题相同的抽象策略,但是我认为这有很大的不同。

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

但是,不变的执法是一个领域关注的问题,您不必深入研究基础架构的详细信息(UserValidtor现在可以这样看)以了解其组成,而从根本上讲,这就是您要做的最终事情,因为规则将以框架术语表达,并且将在领域之外。

为什么要住在外面?

domain -> IUserRepository
infrastructure -> HibernateUserRepository

domain -> IUserValidator
infrastructure -> FluentUserValidator

始终有效的实体

也许您的设计存在一个更根本的问题,如果您遵守以下规则,则您甚至都不会问这个问题:始终有效的实体。

从这个角度来看,不变的执行是域实体本身的责任,因此,即使没有有效的执行,它也不应该存在。因此,不变规则可以简单地表示为合同,而违反则抛出异常。

其背后的原因是,许多错误是由于对象处于它们本不应该处于的状态这一事实而来的。揭露我从格雷格·杨读来的一个例子:

我们的提议,我们现在有一个SendUserCreationEmailService,需要一个
UserProfile......我们怎么能在服务,合理化Name是不是null?我们会再次检查吗?或更可能的是……您只是不想打扰并“希望最好”,您希望有人在将其发送给您之前先进行验证。当然,使用TDD时,我们应该编写的第一个测试之一是,如果我给客户发送了一个null名称,它将引发错误。但是,一旦我们开始一遍又一遍,我们知道写这些类型的测试......“等,如果我们决不允许名称成为空,我们不会有所有这些测试”
- Greg Young的评论上http://jeffreypalermo.com / blog
/始终有效实体的谬误/

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

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

如果现在查看您的代码并尝试应用始终有效的方法,则可以清楚地看到该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);
    }
}

因此,目前在域中没有FluentValidation的位置。如果您仍然不确定,请问自己如何整合价值对象?每次实例化值对象时,您是否都会有一个UsernameValidator验证Username对象?显然,这没有任何意义,使用价值对象很难与非总是有效的方法集成。

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

这实际上是我一直在努力的事情,而且我已经问了自己一段时间了(而且我仍然不完全相信我会说的话)。

基本上,我已经了解到,收集和返回错误不是域的工作,而是UI的问题。如果无效的数据使它到达了域,它只会抛出您的信息。

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

我知道,似乎很难接受会有一定程度的重复,但这主要是因为您可能是像我这样的全栈开发人员,负责处理UI和域,而实际上可以并且应该查看它们作为完全不同的项目。同样,就像视图模型和域模型一样,视图模型验证和域验证可能相似,但用途不同。

另外,如果您仍然担心DRY,那么有人曾经告诉我代码重用也是“耦合”的,我认为这一点在这里特别重要。

处理域中的延迟验证

在这里,我将不再赘述,但是有多种方法可以处理该域中的延迟验证,例如Ward Cunningham用Checks模式语言描述的“规范”模式和“
延迟验证”方法。如果您拥有Vaughn
Vernon的《实施领域驱动的设计》一书,则也可以阅读第208-215页。

始终是权衡问题

验证是一个非常困难的主题,并且证明到今天为止,人们仍然对如何完成验证尚无共识。有很多因素,但是最后您想要的是一个实用,可维护且富有表现力的解决方案。您不能始终是一个纯粹主义者,必须接受某些规则将被破坏的事实(例如,为了使用您选择的ORM,您可能必须泄漏实体中的一些不引人注意的持久性细节)。

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

2020-05-19