整洁的架构:在哪里进行 API 调用
Clean Architecture: Where to make API calls
我目前正在创建一个微服务项目,我在其中实施了 Bob Martin 创造的清洁架构模式。虽然我的代码运行完美,但我对干净的架构模式有疑问,特别是接口和 use_cases 层。该应用程序是我正在开发的一个小型电子商务 POC。话虽这么说,因为它正在实施微服务,所以我有 3 种不同的服务。产品、图像和评论。每当发出获取“完整产品”的请求时,客户端将 ping 完整产品的端点,它会抓住它,并使用它的 id 来 ping 我的图像和评论服务以获取该产品的所有图像和评论。
那么我的问题是,我应该在哪里实现创建这些调用的逻辑?我的直觉告诉我应该把它放在控制器层中,因为它是两者中最抽象的,而且我不会因为将 axios 依赖项放在其中而感到难过。但是,唉,我也觉得 'a full product MUST include the product details, its images, and all reviews' 听起来很像商业规则。
我知道这个问题主要是主观的,但我想知道您将如何实现这个逻辑,为什么?
我还应该提到,在我当前的解决方案中,我的 use_case 是我调用存储库并实际从数据库中获取产品的地方。也就是说,如果我将 API 调用放入控制器层,我必须首先调用我的 use_case 来获取产品,然后可能创建一个单独的 use_case 来检查我的最终对象实际上是“完整产品”。
从您的产品用例的角度获取图像和评论就像访问数据库一样。唯一的区别是您不查询数据库,而是查询另一个服务。但是从您的产品微服务的角度来看,两者都是为您的用例提供数据的外部系统。
当您查看干净的架构时,您会发现控制器和网关位于同一架构层 - 接口适配器。该层被命名为“接口适配器”,因为它适配下层的接口。
如您所见,网关可以是数据库或外部接口(服务)。
因此您应该以这种方式构建您的应用程序:
+------------------+
| product use case |
+------------------+
|
+------------------+---------------------+
| | |
V V V
+-----------------+ +------------------+ +-------------------+
| ImageRepository | | ReviewRepository | | ProductRepository |
+-----------------+ +------------------+ +-------------------+
^ ^ ^
| | |
===========+=====================+====================+================
| | |
+--------------------+ +--------------------+ +--------------------+
| ImageRestClient | | ReviewRestClient | | JDBCConnector |
+--------------------+ +--------------------+ +--------------------+
您可能想选择其他命名,但结构将保持不变。
易于测试,如果有一天您决定在产品微服务而不是单独的服务中管理图像,您可以将 ImageRestClient
替换为 JDBCConnector
。
编辑
@Rene, could you please help to understand. I use clean architecture a lot but so far I don't understand why DB layer is an outer circle in a clean architecture. Yes, it's a communication with an external system, but 1) if DB is a framework, usually the main Application will import DB, not vice versa.
它是依赖倒置原则的应用告诉我们
"High level policy should not depend on low level details." - Robert. C. Martin
应用程序记住数据的方式是一个细节。我说“记住”,因为这是数据存储的抽象。也许数据存储在数据库中,只是一个文件,甚至只存储在 RAM 中。例如。 Web 应用程序可以使用 SessionCartRepository
或 DBCartRepository
来存储购物车。
我猜人们有时对干净的架构有点困惑,因为他们之前看到的图表将 DB 放在了中心。但是干净的架构图只是一种可视化如何构建应用程序的方式。
早在我听说干净架构之前我就已经应用了它,但我的图表看起来像这样。
+--------------------+
| ApplicationService | ---+
+--------------------+ |
| | Use Case
======|===================|================
V V Domain
+------------+ +----------------+
| Repository | --> | BusinessObject |
+------------+ +----------------+
^
=======|====================================
| Database
+----------------+
| JdbcRepository |
+----------------+
好的,2014 年我的命名方式有所不同。我的 ApplicationService
实现了用例,我的 BusinessObject
是 CA 的实体。但结构与 CA 提议的相同。当我第一次阅读 Bob 叔叔的书“干净的架构”时,我认为他的图表要好得多。从那时起我也使用 CA 图。但有时我会使用上面显示的图表,因为有些人出于某种原因喜欢将 DB 层绘制在底部。
- When External Services has Network Models and UseCases have DomainModels, which layer should be responsible to map from Network Models into DomainModels (usually for me it was UseCases layer, but it means that inner layer knows about outer layer). Should External Services layer do that mapping
在两种类型之间映射的组件必须知道这两种类型,因此它对这两种类型都有依赖性。
+------------+ +--------+ +------------+
| SourceType | <---- | Mapper | ----> | TargetType |
+------------+ +--------+ +------------+
因此您不能将映射代码放在实体或用例层中,因为这些层将依赖于外层,例如网络模型是细节。它会违反干净架构的依赖规则。
因此您必须将映射代码放在外层(例如网络层)。
上次编辑
You mentioned that your diagram is a bit old as you used it before. So what is the correct way right now?
Bob 大叔的方式,因为它是广泛使用的方式,只有他的视图被命名为“清洁架构”。我的没有名字。
If I understand correctly now 1) Repository should sit only in outer layer and only use cases can talk to Repositories (via abstraction of course)?
存储库实现放在外层。定义,例如interfaces or abstract 类 放在用例层。换句话说,用例说明它需要什么,例如带接口。像数据库这样的提供者实现它。
- Domain layer is Entities in Clean Architecture? 3) Or should some Repos sit in a domain layer and talk to Repos in DB layer (outer layer) (so it would mean that UseCases don't talk to DB Repos)
这是我听说干净架构之前的“旧”观点。我通常将存储库放在用例旁边。我也通常应用接口隔离原则,这意味着我创建用例特定的存储库。例如
public interface PlaceOrderRepository {
...
}
在我听说 CA 之前,我将存储库定义作为抽象 类 放在域层中,以便我可以使用包修饰符。这是我今天不再做的事情。今天我将工厂放在可以从包范围中受益的实体层中(如果我需要包范围)并让存储库使用这些工厂。
因此我建议在用例用例层定义用例特定的存储库,并在外层——接口适配器层实现它们。
我想这就是这一层被称为接口适配器的原因,因为它是您为在内圈中定义的接口实现适配器的层。
我目前正在创建一个微服务项目,我在其中实施了 Bob Martin 创造的清洁架构模式。虽然我的代码运行完美,但我对干净的架构模式有疑问,特别是接口和 use_cases 层。该应用程序是我正在开发的一个小型电子商务 POC。话虽这么说,因为它正在实施微服务,所以我有 3 种不同的服务。产品、图像和评论。每当发出获取“完整产品”的请求时,客户端将 ping 完整产品的端点,它会抓住它,并使用它的 id 来 ping 我的图像和评论服务以获取该产品的所有图像和评论。
那么我的问题是,我应该在哪里实现创建这些调用的逻辑?我的直觉告诉我应该把它放在控制器层中,因为它是两者中最抽象的,而且我不会因为将 axios 依赖项放在其中而感到难过。但是,唉,我也觉得 'a full product MUST include the product details, its images, and all reviews' 听起来很像商业规则。
我知道这个问题主要是主观的,但我想知道您将如何实现这个逻辑,为什么?
我还应该提到,在我当前的解决方案中,我的 use_case 是我调用存储库并实际从数据库中获取产品的地方。也就是说,如果我将 API 调用放入控制器层,我必须首先调用我的 use_case 来获取产品,然后可能创建一个单独的 use_case 来检查我的最终对象实际上是“完整产品”。
从您的产品用例的角度获取图像和评论就像访问数据库一样。唯一的区别是您不查询数据库,而是查询另一个服务。但是从您的产品微服务的角度来看,两者都是为您的用例提供数据的外部系统。
当您查看干净的架构时,您会发现控制器和网关位于同一架构层 - 接口适配器。该层被命名为“接口适配器”,因为它适配下层的接口。
如您所见,网关可以是数据库或外部接口(服务)。
因此您应该以这种方式构建您的应用程序:
+------------------+
| product use case |
+------------------+
|
+------------------+---------------------+
| | |
V V V
+-----------------+ +------------------+ +-------------------+
| ImageRepository | | ReviewRepository | | ProductRepository |
+-----------------+ +------------------+ +-------------------+
^ ^ ^
| | |
===========+=====================+====================+================
| | |
+--------------------+ +--------------------+ +--------------------+
| ImageRestClient | | ReviewRestClient | | JDBCConnector |
+--------------------+ +--------------------+ +--------------------+
您可能想选择其他命名,但结构将保持不变。
易于测试,如果有一天您决定在产品微服务而不是单独的服务中管理图像,您可以将 ImageRestClient
替换为 JDBCConnector
。
编辑
@Rene, could you please help to understand. I use clean architecture a lot but so far I don't understand why DB layer is an outer circle in a clean architecture. Yes, it's a communication with an external system, but 1) if DB is a framework, usually the main Application will import DB, not vice versa.
它是依赖倒置原则的应用告诉我们
"High level policy should not depend on low level details." - Robert. C. Martin
应用程序记住数据的方式是一个细节。我说“记住”,因为这是数据存储的抽象。也许数据存储在数据库中,只是一个文件,甚至只存储在 RAM 中。例如。 Web 应用程序可以使用 SessionCartRepository
或 DBCartRepository
来存储购物车。
我猜人们有时对干净的架构有点困惑,因为他们之前看到的图表将 DB 放在了中心。但是干净的架构图只是一种可视化如何构建应用程序的方式。
早在我听说干净架构之前我就已经应用了它,但我的图表看起来像这样。
+--------------------+
| ApplicationService | ---+
+--------------------+ |
| | Use Case
======|===================|================
V V Domain
+------------+ +----------------+
| Repository | --> | BusinessObject |
+------------+ +----------------+
^
=======|====================================
| Database
+----------------+
| JdbcRepository |
+----------------+
好的,2014 年我的命名方式有所不同。我的 ApplicationService
实现了用例,我的 BusinessObject
是 CA 的实体。但结构与 CA 提议的相同。当我第一次阅读 Bob 叔叔的书“干净的架构”时,我认为他的图表要好得多。从那时起我也使用 CA 图。但有时我会使用上面显示的图表,因为有些人出于某种原因喜欢将 DB 层绘制在底部。
- When External Services has Network Models and UseCases have DomainModels, which layer should be responsible to map from Network Models into DomainModels (usually for me it was UseCases layer, but it means that inner layer knows about outer layer). Should External Services layer do that mapping
在两种类型之间映射的组件必须知道这两种类型,因此它对这两种类型都有依赖性。
+------------+ +--------+ +------------+
| SourceType | <---- | Mapper | ----> | TargetType |
+------------+ +--------+ +------------+
因此您不能将映射代码放在实体或用例层中,因为这些层将依赖于外层,例如网络模型是细节。它会违反干净架构的依赖规则。
因此您必须将映射代码放在外层(例如网络层)。
上次编辑
You mentioned that your diagram is a bit old as you used it before. So what is the correct way right now?
Bob 大叔的方式,因为它是广泛使用的方式,只有他的视图被命名为“清洁架构”。我的没有名字。
If I understand correctly now 1) Repository should sit only in outer layer and only use cases can talk to Repositories (via abstraction of course)?
存储库实现放在外层。定义,例如interfaces or abstract 类 放在用例层。换句话说,用例说明它需要什么,例如带接口。像数据库这样的提供者实现它。
- Domain layer is Entities in Clean Architecture? 3) Or should some Repos sit in a domain layer and talk to Repos in DB layer (outer layer) (so it would mean that UseCases don't talk to DB Repos)
这是我听说干净架构之前的“旧”观点。我通常将存储库放在用例旁边。我也通常应用接口隔离原则,这意味着我创建用例特定的存储库。例如
public interface PlaceOrderRepository {
...
}
在我听说 CA 之前,我将存储库定义作为抽象 类 放在域层中,以便我可以使用包修饰符。这是我今天不再做的事情。今天我将工厂放在可以从包范围中受益的实体层中(如果我需要包范围)并让存储库使用这些工厂。
因此我建议在用例用例层定义用例特定的存储库,并在外层——接口适配器层实现它们。
我想这就是这一层被称为接口适配器的原因,因为它是您为在内圈中定义的接口实现适配器的层。