清洁架构:在哪里进行 API 调用

Posted

技术标签:

【中文标题】清洁架构:在哪里进行 API 调用【英文标题】:Clean Architecture: Where to make API calls 【发布时间】:2021-05-23 09:02:44 【问题描述】:

我目前正在创建一个微服务项目,在该项目中我实现了 Bob Martin 提出的清洁架构模式。虽然我的代码运行良好,但我有一个关于干净架构模式的问题,特别是接口和 use_cases 层。该应用程序是我正在开发的一个小型电子商务 POC。话虽这么说,因为它正在实现微服务,所以我有 3 个不同的服务。产品、图片和评论。每当请求获取“完整产品”时,客户端将 ping 完整产品的端点,它将抓取该端点,并使用其 id ping 我的图像和评论服务以获取该产品的所有图像和评论。

那么,我的问题是,我应该在哪里实现创建这些调用的逻辑?我的直觉告诉我应该把它放在控制器层,因为那是两者中最抽象的,而且我不会因为把 axios 依赖放在里面而感到难过。但遗憾的是,我也觉得“完整的产品必须包括产品详细信息、图片和所有评论”这句话听起来很像商业规则。

我知道这个问题主要是主观的,但我想知道你是如何实现这个逻辑的,为什么?

我还应该提到,在我当前的解决方案中,我的 use_case 是我调用存储库并实际从数据库中获取产品的地方。话虽如此,如果我将 API 调用放入控制器层,我必须首先调用我的 use_case 来获取产品,然后可能创建一个单独的 use_case 来检查我的最终对象是否实际上是“完整产品”。

【问题讨论】:

【参考方案1】:

从产品用例的角度获取图片和评论就像访问数据库一样。唯一的区别是您不查询数据库,而是查询另一个服务。但从您的产品微服务的角度来看,两者都是为您的用例提供数据的外部系统。

当您查看干净的架构时,您会意识到控制器和网关位于同一架构层 - 接口适配器。这一层被命名为“接口适配器”,因为它适配了下层的接口。

如您所见,网关可以是数据库或外部接口(服务)。

因此,您应该以这种方式构建您的应用程序:

                       +------------------+
                       | product use case |
                       +------------------+
                                |
             +------------------+---------------------+
             |                  |                     |
             V                  V                     V
    +-----------------+ +------------------+ +-------------------+
    | ImageRepository | | ReviewRepository | | ProductRepository |
    +-----------------+ +------------------+ +-------------------+
            ^                     ^                    ^
            |                     |                    |
 ===========+=====================+====================+================
            |                     |                    |
 +--------------------+ +--------------------+ +--------------------+
 |   ImageRestClient  | | ReviewRestClient   | |   JDBCConnector    |
 +--------------------+ +--------------------+ +--------------------+

您可能想选择其他命名,但结构将保持不变。

易于测试,如果您决定有一天应该在产品微服务中而不是单独的服务中管理图像,您可以将ImageRestClient 替换为JDBCConnector


编辑

@Rene,请您帮忙理解一下。我经常使用干净的架构,但到目前为止我不明白为什么 DB 层是干净架构中的外圈。是的,它是与外部系统的通信,但是 1)如果 DB 是一个框架,通常主应用程序会导入 DB,反之亦然。

这是依赖倒置原则的一个应用,它告诉我们

“高级策略不应依赖于低级细节。” - Robert. C. Martin

应用程序记住数据的方式是一个细节。我说“记住”,因为这是数据存储的抽象。也许数据存储在数据库中,只是一个文件,甚至只存储在 RAM 中。例如。 Web 应用程序可以使用SessionCartRepositoryDBCartRepository 来存储购物车。

我想人们有时会对干净的架构感到有些困惑,因为他们之前看到的图表将 DB 放在了中心。但是干净的架构图只是一种可视化如何构建应用程序的方式。

几年前我听说过干净的架构,我已经应用了它,但我的图表看起来像这样。

 +--------------------+
 | ApplicationService | ---+
 +--------------------+    |
       |                   |        Use Case
 ======|===================|================
       V                   V          Domain
 +------------+      +----------------+
 | Repository | -->  | BusinessObject |
 +------------+      +----------------+
       ^
=======|====================================
       |                            Database
 +----------------+   
 | JdbcRepository | 
 +----------------+

好的,2014 年我的命名不同了。我的ApplicationService 实现了用例,我的BusinessObject 是CA 的实体。但结构与 CA 提议的相同。当我第一次阅读鲍勃叔叔的书“清洁架构”时,我认为他的图表要好得多。从那时起,我也使用 CA 图。但有时我会使用上面显示的图表,因为有些人喜欢将 DB 层绘制在底部,无论出于何种原因。

    当外部服务有网络模型并且用例有域模型时,应该由哪一层负责从网络模型映射到域模型(通常对我来说是用例层,但这意味着内层知道外层)。外部服务层是否应该进行映射

在两种类型之间映射的组件必须知道这两种类型,因此它对这两种类型都有依赖关系。

+------------+       +--------+       +------------+
| SourceType | <---- | Mapper | ----> | TargetType |
+------------+       +--------+       +------------+

因此,您不能将映射代码放在实体层或用例层中,因为这些层将依赖于外层,例如网络模型是细节。这将违反干净架构的依赖规则。

因此您必须将映射代码放在外层(例如网络层)。

最后编辑

您提到您的图表有点旧,因为您以前使用过它。那么现在正确的做法是什么?

鲍勃叔叔的做法,因为它是广泛使用的一种,只有他的观点被命名为“清洁架构”。我的没有名字。

如果我现在理解正确 1) 存储库应该只位于外层,并且只有用例可以与存储库对话(当然是通过抽象)?

存储库实现放置在外层。定义,例如接口或抽象类放在用例层。换句话说,用例说明了它需要什么,例如带接口。像数据库这样的提供者实现了它。

    域层是干净架构中的实体? 3) 或者某些 Repos 应该位于域层并与 DB 层(外层)中的 Repos 对话(所以这意味着 UseCases 不与 DB Repos 对话)

这是我在听说干净架构之前的“旧”观点。我通常将存储库放在用例旁边。我通常也应用接口隔离原则,这意味着我创建了特定于用例的存储库。例如

public interface PlaceOrderRepository 
   ...

在听说 CA 之前,我将存储库定义作为抽象类放在域层中,以便可以使用包修饰符。这是我今天不再做的事情。今天我将工厂放在可以从包范围中受益的实体层(如果我需要包范围)并让存储库使用这些工厂。

因此,我建议在用例用例层定义用例特定的存储库,并在外层 - 接口适配器层实现它们。

我想这就是为什么这个层被称为接口适配器的原因,因为它是你为在内圈中定义的接口实现适配器的层。

【讨论】:

很好的答案!太感谢了!确实有助于消除一些混乱。有时很容易混淆 use_case 和 interface 层! @Rene,请您帮忙理解一下。我经常使用干净的架构,但到目前为止我不明白为什么 DB 层是干净架构中的外圈。是的,它是与外部系统的通信,但是 1)如果 DB 是一个框架,通常主应用程序会导入 DB,反之亦然。 2)当外部服务有网络模型和用例有域模型时,哪一层应该负责从网络模型映射到域模型(通常对我来说是UseCases层,但这意味着内层知道外层)。外部服务层应该做那个映射 @PaulT。我更新了我的答案。希望能回答你的问题。如果不是,您可能想就 SO 提出您自己的问题。问候 @RenéLink,谢谢你的详细回答。现在它更干净了,所以 DB 层在外层。您介意回答另一个问题吗?您提到您的图表有点旧,因为您以前使用过它。那么现在正确的方法是什么?如果我现在理解正确 1)存储库应该只位于外层,并且只有用例可以与存储库对话(当然是通过抽象)? 2)领域层是清洁架构中的实体? 3) 或者某些 Repos 应该位于域层并与 DB 层(外层)中的 Repos 对话(所以这意味着 UseCases 不与 DB Repos 对话)

以上是关于清洁架构:在哪里进行 API 调用的主要内容,如果未能解决你的问题,请参考以下文章

清洁架构:用例输出端口

NodeJS Rest API - 调用外部 API 的正确位置在哪里

清洁架构 - 在使用存储库模式和用例刷新的同时获取缓存数据

调用 API 时在哪里使用并发

使用 GraphQL API 的清洁架构 CQRS

使用 Postman 测试 API,Postman 调用在哪里?