我的域模型中的任何给定实体都有几个需要强制执行的不变量——项目名称必须至少有 5 个字符,必须存在某个产品才能与项目关联,截止日期不得早于当前日期和时间等
显然我希望客户端能够显示与验证相关的错误消息,但我不想在程序的几个不同层之间不断维护验证规则——例如,在小部件、控制器、应用程序服务中或命令对象和域。另外,描述性错误消息似乎与表示相关,不属于域层。我该如何解决这些困境?
我的域模型中的任何给定实体都有几个需要强制执行的不变量——项目名称必须至少有 5 个字符,必须存在某个产品才能与项目关联,截止日期不得早于当前日期和时间等
显然我希望客户端能够显示与验证相关的错误消息,但我不想在程序的几个不同层之间不断维护验证规则——例如,在小部件、控制器、应用程序服务中或命令对象和域。另外,描述性错误消息似乎与表示相关,不属于域层。我该如何解决这些困境?
我会创建与您预期的错误条件相关的特定异常。这是一般异常处理的标准,有助于解决您的问题。例如:
public class ProjectNameNotLongEnoughException : System.Exception
或者
public class DueDatePriorToCurrentDateException : System.Exception
在可能抛出异常的方法的 xml 注释中标记这些可能的异常,以便针对您的域模型编写的应用程序将知道要注意这些异常,并能够在应用程序的表示中显示消息。这还允许您根据文化获得本地化的错误消息,而不会因表示问题而使您的域模型混乱。
如果你选择进行客户端验证,恐怕你也吃不下蛋糕。在这种情况下,您可能必须复制验证逻辑才能在维护架构的同时实现所需的功能。
希望这可以帮助!
我意识到这是一个老问题,但这可能会帮助处于类似情况的其他人。
您在此处具有需要封装到域模型中的行为和条件。
例如,我建议对某个长度有要求的 ProjectName 应该封装在 ValueObject 中。对某些人来说,这似乎有些过火,但在我们的领域模型中,我们几乎总是将原生类型(尤其是字符串)封装在一个 ValueObject 中。然后,这允许您在 ValueObject 的构造函数中滚动验证。
在构造函数中,您可以抛出与传入参数冲突相关的异常。这是我们的 ZoneName 值对象之一的示例:
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 定义的。然后,您可以将 Provider 添加到 MVC 中的 ModelValidatorProviders。在此处查看 JBogards ContosoUniversity 示例https://github.com/jbogard/ContosoUniversity/tree/master/src/ContosoUniversity并查看 DependancyResolution 文件夹 DefaultRegistry.cs。
您的其他产品示例必须存在才能链接到项目。在我看来,这听起来像是域服务将是促进 2 个有界上下文之间合作的最佳选择?域服务将确保不变量在有界上下文中保持一致。该域服务不会公开公开,因此您需要一个 ApplicationService 或 CQRS 类型的接口,它将该 DomainService 作为依赖项,允许 DomainService 执行所需的操作。DomainService 应该包含域行为,而应用程序服务应该只是调用该函数的促进者。然后,您的 DomainService 将引发异常,而不是导致不一致或无效的不变量。
您最终应该处于没有重复验证的位置,或者至少您永远不会得到无效的不变量,因为在某些时候没有执行验证,因为验证总是在您的域模型中处理。
虽然描述性错误消息似乎更多地与表示相关而不是业务,但描述性错误消息实际上体现了域模型中包含的业务规则——并且在抛出任何类型的异常时,最好传递一些描述性消息. 此消息可以重新抛出层以最终显示给用户。
现在,当涉及到抢先验证(例如允许用户仅键入某些字符或从特定范围的选项中进行选择的小部件)时,实体可能包含一些常量或方法,这些常量或方法返回可以使用的动态生成的正则表达式通过视图模型,然后由小部件实现。