【问题标题】:Validation against external state in Clean ArchitectureClean Architecture 中针对外部状态的验证
【发布时间】:2022-11-26 13:27:27
【问题描述】:

Clean Architecture 建议在实体/域级别实施“企业范围的规则”。

我正在努力了解如何处理以下情况:在需要确保遵守安全规则的仓库中。例如,酸不得储存在碱性(碱液)之上,反之亦然。或者新鲜的肉类不得存放在没有正常运行空调的区域。

因为这些是适用于全球的安全或卫生规则,所以域级别似乎是实施它们的合适场所。这可能是一个名为“WarehouseBoxUnit”的类,其方法“AddProduct”包括环境条件验证。

但是,在典型的场景中,你必须访问一个外部状态来检查是否有任何酸性产品存储在你想要放碱性的某个存储箱的上方或下方。或者您甚至必须访问传感器数据以检查空调是否正常工作。无论如何,这种状态是动态的和外在的。访问外部数据应该是基础设施的一部分。

冲突在于,在 Clear Architecture 中,域模型不应该引用基础设施项目。无论如何,这可能是一个循环引用。

我可以将验证移至应用程序层,在那里我将拥有基础设施类的接口。但是,这会有风险,因为有人可能会在添加新功能/用例时忘记进行所有必要的验证。

有解决这个问题的既定方法吗?

【问题讨论】:

    标签: c# clean-architecture


    【解决方案1】:

    @rené-link 发布的答案很好地解决了我问题中描述的情况。

    基于此,我想出了一种更通用的方法:

    验证逻辑将在域模型中实现。

    域模型包含一个(或多个)代表从外部世界检索状态信息。这些是验证参数.参数可以是状态,也可以是配置参数(即每个盒子允许的最大重量)。

    将委托放置在应用层的公共区域非常重要,以确保所有用例中的行为一致。委托处理程序通过基础设施的接口检索外部状态或配置数据。

    委托可以在域模型的构造函数或 setter 方法等中启动。在任何情况下,如果设置不正确,测试都会失败。

    例如,这种设计不仅可以解决上述仓库问题,还可以控制在盖子打开时不得启动的机器。如果您有多个用例(从 Web UI 手动启动,从智能设备自动启动等),您可以证明在所有情况下都执行了验证。

    【讨论】:

      【解决方案2】:

      我将创建一个“WarehouseShelf”域对象,用例从存储库中检索该域对象。然后用例可以将 WarehouseBoxUnit 放入货架。如果规则不适用,WarehouseShelf 可以拒绝。如果 WarehouseShelf 接受 WarehouseBoxUnit,您可以将 WarehouseShelf 传递给存储库的持久化方法。

      当存储库创建 WarehouseShelf 时。您不得加载所有 WarehouseShelfBoxUnit 来创建 WarehouseShell。也许创建一个 WharehouseBoxUnitDescribtion 就足够了,它可以判断它是否与您要添加的盒子的盒子描述兼容。

      WarehouseShelf 也可以有一个 WarehouseConditions 对象,可以判断它是否有工作空调。 WarehouseBoxUnit 可以告诉它需要的存储条件,您可以询问 WarehouseConditions 是否满足这些条件。

      所以最后我的域模型看起来像这样:

      +---------+        +--------------------+
      | BoxUnit | ---->  | BoxUnitDescription |
      +---------+        +--------------------+
      
      
      +--------------+     *  +------------+    0..1  +--------------------+
      |     Shelf    | -----> | ShelfSpace | -------> | BoxUnitDescription |
      +--------------+        +------------+          +--------------------+
      | add(BoxUnit) |       
      +--------------+
             |
             |
             V
      +---------------------+
      | WarehouseConditions |
      +---------------------+
      

      Shelf 可能有一个单独的列表来存储添加的 BoxUnit,可能是 ShelfModifications,因为 ShelfSpace 只知道 BoxUnitDescriptions。 或者您可以在 ShelfSpace 中使用 BoxUnit 而不是 BoxUnitDescription。

      在任何一种情况下,实体都与对外部数据的访问分离,当您想要对规则进行单元测试时,您可以简单地创建一个带有 BoxDescriptions、WarehouseConditions 的 Shelf 并向其添加一个 BoxUnit。

      编辑

      另一种方法是使用域名服务Voughn Vernon 将其描述为:

      域中的服务是完成特定于域的任务的无状态操作。通常,您应该在域模型中创建服务的最佳指示是当您需要执行的操作感觉不适合作为聚合或值对象上的方法时。为了减轻这种不舒服的感觉,我们的自然倾向可能是在聚合根的类上创建一个静态方法。但是,在使用 DDD 时,该策略是一种代码味道,可能表明您需要一个服务。

      Voughn Vernon,Implementing Domain-Driven Design,第 7 章服务

      但请注意,使用域服务通常会导致 anemic domain model,我认为您应该避免。这就是为什么我没有在我的答案的第一个版本中提到它。但这可能是适合您情况的解决方案。

      【讨论】:

        猜你喜欢
        • 2022-05-06
        • 2021-06-15
        • 1970-01-01
        • 2022-06-13
        • 2016-01-17
        • 2010-09-25
        • 2018-01-03
        • 1970-01-01
        • 2020-07-10
        相关资源
        最近更新 更多