如何避免在域和应用程序层之间复制验证逻辑?

时间:2013-02-04 22:33:12

标签: validation domain-driven-design dry code-duplication

我的域模型中的任何给定实体都有几个需要强制执行的不变量 - 项目名称必须至少为5个字符,某个产品必须存在才能与项目关联,截止日期不得早于当前日期和时间等。

显然我希望客户端能够显示与验证相关的错误消息,但我不想在程序的几个不同层之间不断维护验证规则 - 例如,在小部件,控制器,应用程序服务或命令对象以及域。此外,似乎描述性错误消息与表示相关且不属于域层。我怎样才能解决这些困境?

3 个答案:

答案 0 :(得分:2)

我会创建与您预期的错误条件相关的特定异常。这是一般的异常处理的标准,将有助于解决您的问题。例如:

public class ProjectNameNotLongEnoughException : System.Exception

public class DueDatePriorToCurrentDateException : System.Exception

在xml注释中标记可能抛出这些异常的方法中的这些可能异常,以便针对您的域模型编写的应用程序将知道注意这些异常,并且能够在应用程序的表示中显示消息。这也允许您根据文化获得本地化的错误消息,而不会因为表示问题而混淆您的域模型。

如果你选择进行客户端验证,我担心你不能吃蛋糕也不能吃。在这种情况下,您可能必须复制验证逻辑,以便在维护架构的同时实现所需的功能。

希望这有帮助!

答案 1 :(得分:1)

我意识到这是一个古老的问题,但这可能会帮助处于类似情况的其他人。

您这里需要将行为和条件封装到域模型中。

例如,我建议在一定长度上有要求的ProjectName应该封装在ValueObject中。对于某些人来说似乎有些过分了,但是在我们的域模型中,我们几乎总是将本机类型(尤其是String)封装在ValueObject中。然后,您可以在ValueObject的构造函数中进行验证。

在构造函数中,您可以引发一个异常,该异常与传入的参数的违反有关。这是我们的ZoneName的ValueObject之一的示例:

public ZoneName(string name)
{

    if (String.IsNullOrWhiteSpace(name))
    {
        throw new ArgumentNullException("Zone Name is required");
    }

    if (name.Length > 33)
    {
        throw new ArgumentException("Zone name should be less than 33 characters long");
    }

    Name = name;
}

现在,该ValueObject的使用者可以在调用构造函数之前执行自己的验证,也可以不执行,但是无论哪种方式,您的不变式都将与模型设计保持一致。

我们在您的域模型中构建验证规则,然后在您的UI中使用验证规则的一种方法是使用Mediatr模块,该模块使用一个模型输入,一个模型输出模式,并允许您为每个模型定义验证器查询或命令模型。这些是使用FluentValidation定义的。然后,您可以在MVC中将Provider添加到ModelValidatorProviders。在https://github.com/jbogard/ContosoUniversity/tree/master/src/ContosoUniversity处查看JBogards ContosoUniversity示例,并查看DependancyResolution文件夹DefaultRegistry.cs。

您必须存在其他产品示例才能链接到项目。在我看来,域服务将是促进2个有界上下文之间合作的最佳选择?域服务将确保不变变量在有限的上下文中保持一致。该域服务不会公开公开,因此您需要一个ApplicationService或CQRS类型的接口,它将该DomainService作为依赖项,从而允许DomainService执行所需的操作。 DomainService应该包含域行为,而Application Service应该只是调用该函数的辅助工具。然后,您的DomainService会抛出异常,而不是导致不一致或无效的不变式。

您最终应该最终停留在没有重复验证的位置,或者至少永远不会出现无效的不变量,因为在某个时候尚未执行验证,因为验证始终在域模型中进行。

答案 2 :(得分:-1)

虽然描述性错误消息似乎与业务相关,但描述性错误消息实际上体现了域模型中包含的业务规则 - 并且当抛出任何类型的异常时,最佳实践是传递一些描述性的消息。可以在层上重新抛出此消息,以最终显示给用户。

现在,当涉及抢先验证(例如允许用户仅键入某些字符或从某个选项范围中选择的窗口小部件)时,实体可能包含一些返回动态生成的正则表达式的常量或方法,可以由视图模型使用,然后由小部件实现。