提问人:Bernardo Benini Fantin 提问时间:11/9/2023 更新时间:11/9/2023 访问量:24
在架构中放置业务逻辑作为数据验证
Placement of business logic as validation of data in architecture
问:
我的网域中有一个预算实体,如果预算处于打开状态,用户可以关闭该实体。这意味着,如果预算今天打开并在七天后关闭,那么在这段时间之间,我可以关闭它,而不是之前(我必须删除它),而不是之后(无法执行任何操作)。
这个时间限制提出了一个疑问:我应该在我的域代码中添加验证,如下所示,还是应该在将访问将访问域的服务的控制器中进行验证?
class Budget {
...
public close(): void {
if (this.isOnPeriod()) {
this.closed = true
}
}
private isOnPeriod() {
if (new Date() < this.closing_date) {
throw new BudgetIsClosedError()
}
if (this.opening_date > new Date()) {
throw new BudgetIsNotYetOpen()
}
return true
}
...
}
我不知道如何解释它,但这似乎是业务逻辑,尽管这种情况(不在期间)似乎永远不会到达域,因为我将对到达控制器的数据进行验证(但请记住,我还不知道所有的业务规则和要求)。
答:
这个时间限制提出了一个疑问:我应该在我的域代码中添加验证,如下所示,还是应该在将访问将访问域的服务的控制器中进行验证?
大多数情况下,您希望所有业务策略都在域模型中表达,而不是分散在代码中。
域模型之外的东西不负责业务策略,但负责向域模型提供信息。
因此,您的示例可能如下所示:
public close(Date today): void {
if (this.isOnPeriod(today)) {
this.closed = true
}
在其他地方,你会有如下所示的代码:
Date today = new Date();
Budget budget = someRepository.get(/* whatever */);
budget.close(today);
这种设计的另一个优点是:测试 Budget::close 是否正确处理其所有边缘情况非常容易,因为在测试中,您可以传递任何您想要的日期。
这肯定是在领域内。无论是通过 ValueObject 还是实体本身,都将强制执行此验证。它是业务不变的,必须位于域上。但是,所有的验证都是一样的吗?关于验证应该在哪里进行存在争议。这是因为快速失败的练习。我有一条规则,即有两种类型的验证:
- 可以在不获取实体的情况下完成验证。
- 必须提取实体才能验证操作。
第一个说你可以在表示层中执行此操作(实际上,应用程序层是一个更好的选择,所有表示模型都应该转换为命令,并且属性验证在此阶段进行),然后再获取聚合以在出现任何基元错误时更快地响应。这是您在不了解现有聚合的情况下进行的输入验证。
第二个说您必须在验证之前获取聚合。某些规则需要您的域的状态。任何使您的域处于不良状态的东西都应该在域本身中阻止。因此,必须将这些验证放在域上。
我应该在我的域层中进行这两种验证吗?这两种类型的验证至关重要,必须完成。所以域不会进入错误的状态,对吧?但没那么快,这要看情况。您有两种选择来处理这种情况:
- 相信您的应用程序层会为域提供有效的输入。这样,您就无需在域中再次验证输入。
- 您不信任应用程序层,并验证所有输入。这样,您就可以重复验证,以确保不会出错,但要以重复为代价。
我认为,如果你的团队同时开发应用程序层和领域层,并且他们相互信任,那么第一个选项会更好。否则,请选择第二个选项。
评论