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



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

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

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

public class ProjectNameNotLongEnoughException : System.Exception

public class DueDatePriorToCurrentDateException : System.Exception

在可能抛出这些异常的方法的xml注释中标记这些可能的异常,以便根据域模型编写的应用程序知道要注意这些异常,并能够在应用程序的表示中显示消息。这还允许您根据区域性本地化错误消息,而不会因为表示问题而使域模型混乱。

如果你选择执行客户端验证,恐怕你不能既吃蛋糕又吃蛋糕。在这种情况下,您可能需要复制验证逻辑,以便在维护架构的同时实现所需的功能。

希望这能有所帮助!

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

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

例如,我建议对某个长度有要求的ProjectName应该封装在ValueObject中。对一些人来说,这可能太过分了,但在我们的域模型中,我们几乎总是在ValueObject中封装原生类型,尤其是String。这样就可以在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的使用者可以在调用构造函数之前执行自己的验证,也可以不执行,但无论哪种方式,不变量都将与模型设计一致。

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

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

你最终应该处于一个没有重复验证的位置,或者至少你永远不会得到无效的不变量,因为验证在某个时候没有执行,因为验证总是在你的域模型中处理。

虽然描述性错误消息似乎更多地涉及表示而非业务,但描述性错误消息实际上体现了域模型中包含的业务规则——当抛出任何类型的异常时,最好传递一些描述性消息。这个消息可以被重新抛出到层中,最终显示给用户。

现在,当涉及到抢先验证时(例如,小部件允许用户只键入特定字符或从特定范围的选项中进行选择),实体可能包含一些常量或方法,这些常量或方法返回动态生成的正则表达式,视图模型可以使用该正则表达式,而小部件又可以实现该正则表达式。

最新更新