【问题标题】:How to check Business logic in Onion Architecture Domain Layer?如何检查洋葱架构领域层中的业务逻辑?
【发布时间】:2016-12-28 00:00:04
【问题描述】:

我一直从事网站开发工作,最近开始使用 MVC 创建一个电子商务项目。

我决定用洋葱架构来开发它。据我了解,我的逻辑分为两个不同的领域:应用程序逻辑和业务逻辑。

如您所知,我有两个地方可以实现这些逻辑:业务逻辑的域层和应用程序逻辑的服务层。而且根据洋葱架构域层与基础设施层等上层没有关系

所以我必须在领域层检查这些业务逻辑,并根据情况抛出适当的异常。例如,“产品价格为正”是一个业务逻辑。此逻辑不需要与数据库有关系。但是如果我想检查“duplicate Name for product”怎么办。我确信我不应该在服务层检查这个逻辑。我应该在哪里以及如何检查这些逻辑。

如果我必须有一个验证层,如何建立这个层的关系? 感谢您的回复。

【问题讨论】:

  • 看到领域驱动设计标签,你是否使用了 DDD 的战术模式(聚合、实体、值对象等)?
  • 是的,我正在使用领域驱动设计架构。

标签: c# model-view-controller domain-driven-design business-logic onion-architecture


【解决方案1】:

我通常在领域层定义存储库接口,例如ICustomer, IOrder,... 和具体对象CustomerRepository, OrderRepository,... 在自己的基础设施层。如果领域层中的方法在正常执行过程中需要从数据库中获取某些东西,那么我会从服务层将适当的存储库接口注入到该方法中,并使用我需要的数据库方法。

有时最好为功能定义自己的接口,例如ICanCheckForDuplicateNames 和方法CheckForDuplicates(string name)(此接口在域层中)。在服务层中,我将实例化一个实现ICanCheckForDuplicateNames 的对象(该对象在基础设施层中定义)并将其作为参数传递给域对象。

如果你不需要数据库方法的结果而只需要调用它,例如Log(string message),那么你可以在领域模型中定义一个事件并在执行过程中引发它,服务层会处理它。这样你就不需要从服务层向领域层类方法传递任何东西。

【讨论】:

  • 请记住,这些检查可能总是陈旧的,并且您不能保证在进行查询后数据没有更改。
  • 是的,在这种情况下,服务层应该将所有内容包装在事务中。
  • 谢谢。这是我想要的解决方案。
  • @Afsaneh 如果您在事务中将唯一性检查和插入或更新包装在一起,那么您可能会在执行期间锁定大量资源(通常是整个表)交易。这可能会在高度协作的环境中导致争用和并发问题。
  • @guillaume31 - 诚实的问题,在这种情况下使用事务的替代方法是什么?
【解决方案2】:

DDD 的核心概念之一是aggregate - 本质上是一个实体或一组实体,可以以事务一致的方式强制执行一组[不变量]。

不变量可以定义为必须在任何时候都为真的业务规则,除了在特殊时期,例如在业务交易正在进行时。

但是,更具体的定义是查看聚合的不变量 - 这些是始终强制执行的业务规则 - 单个聚合或操作中。这意味着不变量必须基于聚合中包含的数据或通过命令或方法参数传递到聚合中的数据来强制执行。

描述这一点的一种方式是在聚合的一致性边界内强制执行不变量。之所以称为一致性边界,是因为通过遵循 DDD 建议并且每次数据库提交只修改一个聚合,数据一致性只能在该边界内得到保证。

请参阅 http://www.informit.com/articles/article.aspx?p=2020371&seqNum=1 了解有关聚合和不变量的优秀文章。

在整个系统中强制执行唯一约束是一项常见要求 - 但如果您考虑一下,执行唯一名称约束不可能是单个聚合的责任,因为它需要了解所有其他实例要检查的聚合 - 因此违反了一致性边界的原则。

有几种方法可以解决此类问题:

  • 接受规则可能并不总是被强制执行,但它总是“最终”被强制执行(一个被称为最终一致性的概念)。

    • 您可以通过名称的设置引发事件,然后触发执行逻辑以检查重复和标记或警报,以便 UI 可以将用户的注意力吸引到需要注意的项目以更正重复名称。
  • 采取务实的方法并确定在聚合范围之外执行此类规则的最有效位置

    • 例如执行唯一约束的传统且非常成功的地方是在数据库本身 - 大多数 RDBMS 允许在字段上指定唯一约束。由于将在检查聚合名称的数据库事务范围内检查唯一约束,这确实为您提供了此业务规则的原子一致性。
    • 此选项仅限于所有数据都在一个数据库中的情况 - 如果您已分片,则可能需要采用第一个选项

说实话,第二个选项是我一直为“普遍唯一”规则所做的——如果它们确实需要的话——并且数据集的大小使得我期望单个数据库。第一步是质疑它们是必需的假设,并检查与聚合相关的用户故事,以了解真实的用户需求以及他们将容忍的最终一致性水平。

【讨论】:

  • 我将添加第三个选项,即找出不变量所在的新聚合(例如,ProductCatalog) - 请参阅thinkbeforecoding.com/post/2009/10/28/…
  • 亲爱的克里斯,感谢您的回答。是否将验证器传递给实体的构造函数进行检查。例如:“重复名称”。验证器本身在持久层中实现,并在服务层中注入并通过其接口传递。并且 IxRepository 接口也被注入到这个验证器类中。
  • 这是一种方法 - 但是,存在风险,具体取决于您的持久层的并发级别和锁定策略。如果您默认使用乐观并发,那么该方法会给您留下一个竞争条件,其中两个大致同时处理的请求都将使用验证器来检查是否有重复,它将通过检查,并且它们将都保存到数据库中 - 给您留下重复的记录,无法检测或纠正它。
  • 为避免,您需要使用带有整个表锁的悲观锁定 - 这意味着一旦一个进程开始,下一个到达的进程将被阻塞,直到第一个进程完成。这将为您提供一致性,但会以巨大的性能成本为代价。理解这一点的核心是,使用这种技术,您试图在聚合的一致性边界之外的数据中强制执行不变量。正如我在回答中所讨论的,这违反了 DDD 的原则,即仅在聚合内强制执行一致性,并保持聚合较小。
  • 如果您不能或不喜欢在数据库中放置唯一约束,那么最好的选择是处理最终的一致性 - 让您在域层的操作引发域事件 ProductNameChanged -理想情况下,这应该使用服务总线异步处理。处理此事件应查找重复项,然后回滚更改(如果您保留了旧值)或将实体标记为无效并要求用户更正它,然后才能对实体执行其他操作。
猜你喜欢
  • 2013-06-30
  • 2015-03-17
  • 2016-08-12
  • 1970-01-01
  • 1970-01-01
  • 2020-04-14
  • 2011-08-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多