繁体   English   中英

域驱动设计中的验证

[英]Validation in Domain Driven Design

我们的团队正在按照领域驱动设计(DDD)启动一个新项目。 在高层,我们在域的顶部有一个API,使客户端可以在域上执行操作。 我不太清楚的问题之一是,我们在哪里对DDD中的某个属性/属性执行验证。

考虑这个例子。 让我们说,我的API公开了一个以下数据合同/ DTO:

 public class Person
 {
    public string Email { get; set;}
    public string Name { get; set; }
 }

现在,我们要进行业务验证,以防止用户输入无效的电子邮件地址,并限制用户的名称超过50个字符。

为此,我可以看到以下三种方法:

方法1中 ,我们仅在API上进行数据验证(通过数据注释或流利验证)。 我不会在我的域中重复验证。 从理论上讲,这可能意味着我的域可能处于无效状态。 但是,由于正在验证入口点(API),因此在实际情况下是不可能的。

在此处输入图片说明

方法2中 ,我们同时在API和我的域中进行数据验证。 这种方法有助于我们完全消除我的域和API之间的耦合。 API可以独立地向客户端返回错误请求。 并且由于域再次执行了验证,因此域没有机会进入无效状态。 但是,这种方法违反了DRY原理。

在此处输入图片说明

方法3中 ,我们仅在Domain上执行验证,而不在API级别上在DTO上执行验证。 使用这种方法,虽然我们没有重复验证,但是当API调用试图将其置于无效状态时,域无法引发异常。 相反,我们需要将该异常包装在某些Result对象中。 这将有助于API向客户端发送适当的响应(例如,错误请求而不是内部服务器错误)。 我对这种方法不满意的是,我宁愿抛出一个硬异常,而不是放一个包装器。 在此处输入图片说明

  • 哪种方法最有意义,为什么?

  • 业务验证和业务规则之间的界线在哪里? (假设业务规则存在于域中)。

  • 有什么明显的我想念的地方吗?

注意:此问题可能看起来与“ 域驱动设计中的验证”类似,应将输入验证放在域驱动设计中的何处? 但它并不能真正回答具体问题。

现在,让我们说我们有一条业务规则,该规则禁止用户输入无效的电子邮件地址,并限制用户的名称超过50个字符。

在此示例中要注意的重要事项

  • 您没有此数据的权限。 用户名和电子邮件地址都在其他地方分配和管理
  • 就领域模型而言,该数据是不透明的; 您可能永远不会操纵它,也不会根据内容改变任何计算方式。 就您的业务规则而言,它们只是您要复制的哈希值,以便您可以将它们传递给其他值(在信封上打印名称或发送电子邮件)。

从语义上讲,这两个值基本上都是“标识符”的形式。

在这种情况下,域模型根本不关心验证,除了诸如内存不足之类的问题之外。 如果您有固定长度的列或类似的东西,您的数据模型可能会在意。

因此,这很容易成为您关注消息边界而不是域本身的地方之一。

但这并不是验证可能存在的一般问题的良好代理。

将此情况与存款金额进行比较-这是一个数字,您可以合理地期望将其与其他数字相加/相减,然后将其与其他数字相比较,依此类推。 在这里,您可能会看到类似Integer.MAX ,并得出合理的结论,即攻击/数据输入错误比真实用例的可能性更大,您将完全消除该选项。

消息边界的验证主要受以下问题驱动:您可以信任源吗? 如果有任何疑问,那么毫无疑问。 (关于域驱动安全性的Deogun和Johnnson是一个很好的起点)。

在很大程度上,在消息边界的验证归结为确定您收到的字节序列实际上与消息模式兼容。 当然可以包括对允许值范围的限制。 (示例:HTTP响应包括状态码,但您无需假装状态码为777的响应就可以改善您的下午时间)。

因此,声明消息中的名称字段不超过50个字符,并且消息中的电子邮件地址字段符合RFC 5322addr_spec的定义是完全合理的事情。

然后,在边界处,确保所获取的字节实际上满足消息约束,如果可以,则将其传递很长时间。

但是在域模型内? 如果您不需要对数据做假设,那就完成。 “应用程序说这些是字节?对我来说足够好了!”

从技术上来讲,更关键的测试是域模型是否具有满足其前提条件才能确保其结果正确性的前提条件 如果我们有前提条件,那么验证可以作为检测违规行为的一种受控方式。

但是,没有很多不需要先决条件检查的增值注入域模型。

(再次,与amount形成对比-领域模型对于在开始乱扔钱之前发现违规行为非常感兴趣)。

从理论上讲,这可能意味着我的域可能处于无效状态。

我认为名称长度不超过50个字符不会表示无效的域状态...该域仍然可以正常运行。

您必须区分输入验证(数据适合技术插槽吗?)和域不变式。 您可以在API级别上验证的某些内容与域无关,而其他一些则必须通过加载域数据进行检查,因此外层不容易访问。

它们实际上是两组(或更多组)不同的规则,与您想像的相比,它们之间的联系程度更大。

TL; DR-没有确定的答案。 尝试对特征进行更深层次的描述,而不仅仅是“业务验证”,并根据规则类型明智地选择方法1、2或3

哪种方法最有意义,为什么?

方案2。域模型应始终进行验证。 在应用程序层(API),您可以执行此操作或不执行该操作,但是如果可以的话,最好执行此操作,因为您无需调用域即可提前检测到无效数据。 我之所以说“如果可以”,是因为可能存在无法访问域而无法验证的业务规则。

注意:我采用的是验证操作而非数据的方法。 给定的数据对于执行操作可能是有效的,但对其他操作则无效。

业务验证和业务规则之间的界线在哪里? (假设业务规则存在于域中)。

我认为您是误解/混淆的概念。 一个概念是“业务规则”,另一个概念是“验证”。 验证是我们用于检查业务规则是否得到满足的过程。

我仔细阅读了这里专家提供的答案,在团队中进行了许多审议之后,我们决定考虑以下关键原则来进行验证。

  • 我们将API验证和域验证/业务规则视为单独的关注点。 我们将API验证的方式与没有DDD时的方式相同。 这是因为,API只是与我们的域通信的接口。 明天,我们可以在它们之间放置一条消息总线,但是我们的域逻辑不会改变。 API验证包括但不限于字段长度检查,正则表达式检查等。

  • 域验证仅限于业务规则。 字段级别的验证(例如长度检查,正则表达式等)对业务没有任何意义,因此不属于业务规则。 如果存在违反业务规则的情况,我们可以简单地抛出DomainException而不关心消费者如何处理它。 如果是API响应,则意味着将状态码500返回给客户端。

  • 由于我们现在将API验证和业务规则视为一个单独的问题,因此,这确实意味着有重复某些验证规则的机会。 但是我们可以接受,因为它使事情变得更加简单和容易推断。 由于它们是松散耦合的,因此我们可以轻松地独立更改API验证或业务规则,而不会影响彼此。

  • email这样的边界案例很少,有人会争辩说我们域中有ValueObject 但是,我们将视具体情况而定。 例如,我们目前在域中看不到任何具有用于email字段的单独ValueObject值。 因此,仅在API级别对其进行验证(正则表达式检查)。 如果将来如果企业提出其他规则,例如限制在特定领域,那么我们可能会重新考虑我们的决定。

牢记上述原则已帮助我们使事情简单易懂。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM