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

2024-01-03

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

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

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

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


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

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

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

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

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

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

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


EDIT

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

它是依赖倒置原理的应用,它告诉我们:

“高层政策不应依赖于低层细节。” - 罗伯特. C·马丁 https://cleancoders.com/episode/clean-code-episode-13

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

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

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

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

好吧,2014 年我的命名有所不同。我的ApplicationService实施的用例和我的BusinessObject是 CA 的实体。但其结构与 CA 提议的相同。当我第一次读鲍勃叔叔的书《干净的架构》时,我认为他的图表要好得多。从那时起我也使用CA图。但有时我会使用上面显示的图表,因为有些人喜欢将数据库层出于某种原因绘制在底部。

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

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

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

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

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

最后编辑

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

鲍勃叔叔的方式,因为它是广泛使用的方式,并且只有他的观点被称为“清洁架构”。我的没有名字。

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

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

  1. 领域层是干净架构中的实体? 3)或者一些Repos应该位于领域层并与DB层(外层)中的Repos对话(因此这意味着UseCases不与DB Repos对话)

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

public interface PlaceOrderRepository {
   ...
}

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

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

简洁的架构:在哪里进行 API 调用 的相关文章

  • 调用 Mongoose 插件内模式的静态方法

    我写了一个插件 可以执行以下操作 module exports function schema options schema statics customFunction function criteria Code 这是我的架构 var
  • 通过套接字的身份验证方法

    我正在尝试通过套接字进行身份验证sailsjs and passport 挑战似乎在于套接字连接没有会话 并且 sailsjs 模拟请求对象 导致它没有 Passport 中间件设置 这导致nodejs抛出一个错误 说req对象没有调用的方
  • 解析函数是否异步传递给 Promise 执行器?

    我有以下代码 function myPromiseFunc return new Promise resolve gt resolve Promise resolve 123 据我们所知Promise resolve方法立即用普通值解析 P
  • 生成源映射时出错 - grunt 和 sass 配置

    我正在尝试将 sass 与 grunt 一起使用 我已经在我的路径中安装了 ruby sass 和 grunt 版本是 节点 0 10 20npm 11 3 1grunt cli 0 1 13咕噜声 0 4 5萨斯 3 4 4 我的包 js
  • 微服务与 SOA 的不同之处 [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我一直在寻找 SOA 和微服务架构风格之间的差异 并找到了一个很好的链接https www infoq com articles boot mi
  • 确定是否向 Firebase 实时数据库添加或删除数据

    每当添加新帖子时 我都会尝试将通知推送到 Android 应用程序 但是 只要数据 更改 即即使帖子被删除 我不需要 通知也会到达 我如何设置一个条件 以便 FCM 仅在添加帖子时才发送通知 这是我的 index js 文件 const f
  • 启动nodejs时出错:openssl配置失败

    启动 Express 节点时出现以下错误 openssl 配置失败 错误 02001003 系统库 fopen 没有这样的 过程 节点无论如何都会启动 我没有尝试使用 SSL 这是起始代码 app Express app set port
  • 我应该在 Promise.all 中使用 wait 吗?

    我正在构建快速中间件 以对数据库进行两次异步调用 以检查用户名或电子邮件是否已在使用中 这些函数返回的承诺没有捕获 因为我想将数据库逻辑与 req res next 逻辑分开 并且我有集中的错误处理 需要next作为一个论点 在我对本地环境
  • 如何在 Google App Engine 上部署 1 个实例

    我需要在 Google App Engine 上部署一个简单 Node js 应用程序的 1 个实例 无需任何形式的扩展 我试过做gcloud preview app deploy 但是即使在我尝试关闭它们之后 也会创建许多实例 我的目标是
  • 获取请求的客户端 IP 地址而不是 Cloudflare 的 IP 地址

    Cloudflare 会更改传入请求的 IP 地址 因为 Cloudflare 是我的网站和互联网之间的中间件 代理 我该怎么办获取请求的初始IP地址 而不是 Cloudflare 的 IP 地址 我听说过mod cloudflare但是这
  • EJS在JS onload函数中访问express变量

    我知道你可以像这样获取 ejs 文件中变量的值 h1 h1 如果我要在同一个 ejs 页面的 onload javascript 函数中使用相同的标题变量 我将如何使用它 例如 这个函数产生一个控制台错误说 未捕获的语法错误 意外的标识符
  • 无法在渲染器进程中使用 Node.js API

    无法在 Electron 中使用任何与 Electron 或节点相关的操作 未定义获取错误过程 我检查了他们指导添加节点支持的各个地方 但这已经完成了 所以卡在这里 我的主要应用程序代码是 const electron require el
  • NodeJS:如何获取服务器的端口?

    您经常会看到 Node 的示例 hello world 代码 它创建一个 Http Server 开始侦听端口 然后执行以下操作 console log Server is listening on port 8000 但理想情况下你会想要
  • 从 readFile 返回未定义[重复]

    这个问题在这里已经有答案了 我正在尝试让以下代码工作 use strict var fs require fs var fileName readme txt var str fs readFile fileName utf8 functi
  • tsconfig.json 中模块类型的区别

    在 tsconfig json 中 compilerOptions target es5 module commonjs moduleResolution node sourceMap true emitDecoratorMetadata
  • 使用node.js安装xml2json时出错

    我尝试为 node js 安装 xml2json 包 但它给了我错误 Error are as below 我的系统配置如下 Node js 版本 v5 4 1 npm 版本 3 3 12 操作系统 Windows 10 64 位 pyth
  • 是否有“npmpublish-f”的解决方法

    现在npm publish f已弃用 是否有解决方法或软件包可以覆盖发布后的目标版本 我知道关于semver http semver org 我还想要npm publish f 您可以取消发布特定版本 然后重新发布它 npm unpubli
  • 如何获取 vuejs 组件单元测试中定义的“this”变量

    我正在尝试在 npm 脚本中使用 mocha webpack 来测试 vuejs 组件 我在测试中像这样嘲笑 vuex 商店 const vm new Vue template div div
  • aws - 将字符串作为文件上传到 S3 存储桶

    我尝试使用适用于 NodeJS 的 AWS 开发工具包将字符串作为文件保存到 AWS S3 存储桶 PUT 请求成功 但文件未在 S3 存储桶中创建 以下是我的代码片段 const s3 new S3 apiVersion 2006 03
  • 无法运行 npm install

    In here http devdocs magento com guides v2 0 frontend dev guide css topics css debug html它说要跑npm install 但是当我运行时出现此错误sud

随机推荐

  • 如何通过 Jenkins 脚本控制台设置“扫描组织触发器”?

    我需要一种通过 Jenkins 脚本控制台设置 扫描组织触发器 的方法 这很接近 但仅显示多分支或组织扫描触发器 如果它们已存在 https github com cloudbees jenkins scripts blob master
  • 在 Jetty 中从 http 重定向到 https

    我想从 http myurl 永久重定向到 https myurl 但在 Jetty 中我只找到 MovedContextHandler 用它我只能重定向上下文路径 例如从 myurl bla 到 myurl bla bla
  • 线程和执行器的正常关闭

    下面的代码试图实现这一点 该代码永远循环并检查是否有任何待处理的请求 如果有 它会创建一个新线程来处理请求并将其提交给执行器 所有线程完成后 它会休眠 60 秒 并再次检查待处理的请求 public static void main Str
  • 重写 Rust 中的总体特征实现

    对于我的游戏规则引擎 我有一个核心特征 称为Rule处理游戏回调 有两种类型Rules a BaseRule适用于游戏中的任何实体 并且CreatureRule仅适用于生物 我目前的代码结构如下 trait BaseRule
  • 具有多个倒计时器的 Recyclerview 导致闪烁

    我想显示我的每个细胞还剩下多少时间RecyclerView 为此 我为每个都使用了倒计时器 在每一行中我启动一个计数器并管理onTick 一切都按预期工作 我的每一行都有一个计时器滴答声 我的单元格也在更新 但我的单元格现在闪烁 当我滚动时
  • Python 动态装饰器 - 为什么有这么多换行?

    所以我对 Python 装饰器还是有点陌生 我以前用过它们 但我从未制作过自己的装饰器 我正在阅读本教程 http www siafoo net article 68 run time tranformations 那个特定的段落 我似乎不
  • 在 Swift 中使用块给出错误“变量在其自己的初始值内使用”

    这是我在 obj c 中的代码 block NSString requestReference self operation method url url parameters parameters headers headers succ
  • 为什么 SQLException 没有捕获 SQLiteExcpetion?

    我的一个 Android 应用程序中有一些代码可以捕获 SQLException 但我最近发现它没有捕获 SQLiteException 显然 SQLiteException 是 SQLException 的子级 那么为什么它没有被捕获呢
  • 为什么内联元素只有上边框溢出?为什么不离开边框?

    我有一个 div 容器 固定宽度和高度 其中包含一个边框为 10px 的内联元素 内联元素的上边框溢出 为什么不离开边框 下面是我的代码 container width 100px height 100px border 1px solid
  • Java 中 Math.max(a,b) 或 (a>b)?a:b 更快吗?

    Java 中哪一个更快 为什么 Math max a b a gt b a b 这是在采访中被问到的 Here http grepcode com file repository grepcode com java root jdk ope
  • RichTextBox 中的 HTML 格式

    我一直在使用从 XML 文件获取的 HTML 字符串 我正在尝试找出一种方法来在带有格式的富文本框中显示这些字符串 所以 例如 p This is a strong HTML strong string from the em XML em
  • 如何在C#中显示文件夹中所有文件的统计信息[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 输出必须是这样的 文件位于 C Windows 文件总数 49 所有文件的总大小 7121424 字节 最大文件 explorer exe
  • 未提供所需的模拟级别,或者提供的模拟级别无效

    我在 WCF 服务和模拟方面遇到了一些问题 我将其提炼为下面的一个简单方法 WCF 服务当前在 exe 中自行托管 异常消息是 未提供所需的模拟级别 或者提供的模拟级别无效 检查何时抛出错误 身份模拟级别设置为委派 如我的客户端上指定的那样
  • Python“sys.getsizeof”在从列表/字典中删除项目后报告相同的大小?

    我注意到 当使用 sys getsizeof 检查列表和字典的大小时 会发生一些有趣的事情 i have a 1 2 3 4 5 大小为 56 字节 空列表的大小为 36 所以这是有道理的 因为 20 5 4 但是 在我删除列表中的所有项目
  • 如何在NestedScrollView中实现ConstraintLayout

    ConstraintLayout is behaving inconsistently if a TextView has very fewer texts than other TextView wrapped in horizontal
  • 用于 Java 开发的最佳 Eclipse 版本 [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 用于 Java 开发的 Eclipse 有许多版本 我正在尝试 MyEclipse 其开箱即用的体验给我留下了深刻的印象 在我适应它之前 我想知
  • 处理 ASP.NET Core 3.1 中未处理的异常

    在 ASP NET Core 3 1 中添加了一项功能 可以将未处理的异常传递到 ILogger 的实例 如下所示 登录 NET Core 和 ASP NET Core https learn microsoft com en us asp
  • 异质平等的一致性

    我正在尝试使用异构相等来证明涉及此索引数据类型的语句 data Counter Set where cut i j Counter suc i j 我能够使用以下方式编写我的证明Relation Binary HeterogenousEqu
  • 如何在浏览器中播放MKV文件?

    我有一个视频文件MKV格式 我想在浏览器中播放该文件而不进行转换 如何在浏览器中播放该文件格式
  • 简洁的架构:在哪里进行 API 调用

    我目前正在创建一个微服务项目 在其中实现 Bob Martin 创造的清洁架构模式 虽然我的代码运行良好 但我对干净的架构模式有疑问 特别是接口和 use cases 层 该应用程序是我正在开发的一个小型电子商务 POC 话虽如此 由于它正