在架构中放置业务逻辑作为数据验证

Placement of business logic as validation of data in architecture

提问人:Bernardo Benini Fantin 提问时间:11/9/2023 更新时间:11/9/2023 访问量:24

问:

我的网域中有一个预算实体,如果预算处于打开状态,用户可以关闭该实体。这意味着,如果预算今天打开并在七天后关闭,那么在这段时间之间,我可以关闭它,而不是之前(我必须删除它),而不是之后(无法执行任何操作)。

这个时间限制提出了一个疑问:我应该在我的域代码中添加验证,如下所示,还是应该在将访问将访问域的服务的控制器中进行验证?

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
  }

  ...
}

我不知道如何解释它,但这似乎是业务逻辑,尽管这种情况(不在期间)似乎永远不会到达域,因为我将对到达控制器的数据进行验证(但请记住,我还不知道所有的业务规则和要求)。

验证领域 驱动设计 业务逻辑

评论


答:

1赞 VoiceOfUnreason 11/9/2023 #1

这个时间限制提出了一个疑问:我应该在我的域代码中添加验证,如下所示,还是应该在将访问将访问域的服务的控制器中进行验证?

大多数情况下,您希望所有业务策略都在域模型中表达,而不是分散在代码中。

域模型之外的东西不负责业务策略,但负责向域模型提供信息。

因此,您的示例可能如下所示:

  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 是否正确处理其所有边缘情况非常容易,因为在测试中,您可以传递任何您想要的日期。

1赞 R.Abbasi 11/9/2023 #2

这肯定是在领域内。无论是通过 ValueObject 还是实体本身,都将强制执行此验证。它是业务不变的,必须位于域上。但是,所有的验证都是一样的吗?关于验证应该在哪里进行存在争议。这是因为快速失败的练习。我有一条规则,即有两种类型的验证:

  1. 可以在不获取实体的情况下完成验证。
  2. 必须提取实体才能验证操作。

第一个说你可以在表示层中执行此操作(实际上,应用程序层是一个更好的选择,所有表示模型都应该转换为命令,并且属性验证在此阶段进行),然后再获取聚合以在出现任何基元错误时更快地响应。这是您在不了解现有聚合的情况下进行的输入验证。

第二个说您必须在验证之前获取聚合。某些规则需要您的域的状态。任何使您的域处于不良状态的东西都应该在域本身中阻止。因此,必须将这些验证放在域上。

我应该在我的域层中进行这两种验证吗?这两种类型的验证至关重要,必须完成。所以域不会进入错误的状态,对吧?但没那么快,这要看情况。您有两种选择来处理这种情况:

  1. 相信您的应用程序层会为域提供有效的输入。这样,您就无需在域中再次验证输入。
  2. 您不信任应用程序层,并验证所有输入。这样,您就可以重复验证,以确保不会出错,但要以重复为代价。

我认为,如果你的团队同时开发应用程序层和领域层,并且他们相互信任,那么第一个选项会更好。否则,请选择第二个选项。