跳到主内容

Docker MariaDB 整洁架构

Github StackBlitz

一个使用 Fiber 实现的稍微复杂的 REST 应用,展示了整洁架构,其中 MariaDB 作为依赖项,并使用 Docker 进行容器化。

先决条件

  • Docker Compose 用于运行应用。
  • 支持 shmakecurl 的 Shell,用于端到端测试。UNIX 系统或 WSL 应该可以正常工作。
  • Postman,如果您想使用 GUI 测试此 API。

应用

此应用是一个稍微复杂的 REST API 示例,它有四个主要端点。公共用户可以访问 UserAuthMisc 主要端点,但他们无法访问 City 端点(因为它受到保护)。如果有人想访问该端点,他们必须先通过 Auth 端点登录,之后才能访问 City 端点。

此应用使用 MariaDB 作为数据库(容器化),并使用 JWT 作为认证机制。此应用还展示了如何在整洁架构中执行一对多关系映射(一个用户可以拥有多个城市),以及如何在 Go 中实现 JOIN SQL 子句。

整洁架构

Clean Architecture

整洁架构是由 Robert C. Martin(也称为 Uncle Bob)引入的一个概念。简单来说,此架构的目的是实现职责的完全分离。以此方式构建的系统可以独立于框架、可测试(易于编写单元测试)、独立于 UI、独立于数据库以及独立于任何外部机构。当您使用此架构时,更改 UI、数据库或业务逻辑都变得简单。

使用此架构时应牢记的一点是关于依赖规则。在整洁架构中,源代码依赖只能指向内部。这意味着系统的“内圈”完全不能了解外部世界。例如,在上图中,用例知道实体,但实体不能知道用例。外圈中使用的数据格式不应被内圈使用。

正因为如此,当您更改位于最内圈的内容(例如实体)时,通常您必须更改外圈。然而,如果您更改的不是最内圈的内容(例如控制器),您不需要更改用例和实体(您可能必须更改框架和驱动程序,因为它们相互依赖)。

如果您想了解更多关于整洁架构的信息,请参考我下方附上的文章。

系统架构

为了清晰起见,这是展示此 API 系统架构的图表。

System Architecture

请参考下表,了解此应用中使用的各层及其术语/文件名。项目结构参考自此项目。在 internal 包中,根据功能职责对包进行了分组。如果您打开包,您将看到代表整洁架构层的文件。

依赖图是直观的:handler/middleware 依赖于 service,service 依赖于 repository,repository 依赖于 domain 和数据库(通过依赖注入)。所有这些层都通过上面图片中提到的基础设施(Fiber、MariaDB 和认证服务)实现。

我稍微修改了此应用中的层,以符合我自己的整洁架构偏好。

架构层对应层文件名
外部接口展现器和驱动程序middleware.gohandler.go
控制器业务逻辑service.go
用例仓库 (Repositories)repository.go
实体 (Entities)实体 (Entities)domain.go

基本上,一个请求首先会通过 handler.go(和 middleware.go)。之后,程序会调用 service.go 所请求的 repository 或 use-case。该控制器 (service.go) 会调用符合 domain.gorepository.go,以便完成 service.go 所请求的任务。请求的结果将由 handler.go 返回给用户。

简而言之

  • handler.gomiddleware.go 用于接收和发送请求。
  • service.go 是业务逻辑或控制器(有些人可能有不同的看法,但这只是我的主观意见)。
  • repository.go 用于与数据库交互(用例)。
  • domain.go 是程序使用的数据模型的“形状”。

为了完整起见,以下是项目结构的功能职责。

  • internal/auth 用于管理认证。
  • internal/city 用于管理城市。此端点受到保护
  • internal/infrastructure 用于管理应用的基础设施,例如 MariaDB 和 Fiber。
  • internal/misc 用于管理杂项端点。
  • internal/user 用于管理用户。此端点不受保护

请参考代码本身获取更多详细信息。我在代码中添加了所有注释,希望足够清晰!

API 端点 / 功能

此 API 分为四个“主要端点”,分别是杂项、用户、认证和城市。

杂项

此处分类的端点是杂项端点。

  • GET /api/v1 用于健康检查。

用户

此处分类的端点是用于对“用户”领域执行操作的端点。

  • GET /api/v1/users 获取所有用户。
  • POST /api/v1/users 创建用户。
  • GET /api/v1/users/<userID> 获取特定用户。
  • PUT /api/v1/users/<userID> 更新特定用户。
  • DELETE /api/v1/users/<userID> 删除特定用户。

认证

此处分类的端点是用于执行认证的端点。在我看来,这属于框架层/实现细节,因此此端点没有“领域”,您可以将此端点作为其他端点的增强。此 API 中的认证使用 JSON Web Tokens 完成。

  • POST /api/v1/auth/login 作为数据库中 ID 为 1 的用户登录。将返回 JWT,该 JWT 将存储在 cookie 中。
  • POST /api/v1/auth/logout 退出登录。此路由将从 cookie 中移除 JWT。
  • GET /api/v1/auth/private 访问受保护的路由,显示当前(有效)JWT 的信息。

城市

此处分类的端点是用于对 City 领域执行操作的端点。此处端点通过 cookie 中的 JWT 受到保护,因此如果您要使用此端点,请确保您先登录(或至少拥有有效的 JWT)。

  • GET /api/v1/cities 获取所有城市。
  • POST /api/v1/cities 创建新城市。
  • GET /api/v1/cities/<cityID> 获取特定城市。
  • PUT /api/v1/cities/<cityID> 更新特定城市。
  • DELETE /api/v1/cities/<cityID> 删除特定城市。

安装

为了运行此应用,您只需执行以下命令。

  • 克隆仓库。
git clone git@github.com:gofiber/recipes.git
  • 切换到此仓库。
cd recipes/docker-mariadb-clean-arch
  • 立即使用 Docker 运行。运行此命令后,迁移脚本将自动运行,以填充您的 Docker 化 MariaDB。
make start
  • 使用 Postman 测试(将请求 URL 设置为 localhost:8080)或使用创建的端到端测试脚本进行测试。请记住,端到端脚本仅适用于首次运行。如果您尝试第二次运行,您可能无法获得所有完美的结果(因为 MariaDB 中的自动递增)。如果您想再次运行测试套件,请先运行 make stopmake start
make test
  • 拆除或停止容器。这将同时删除创建的 Docker 数据卷和镜像。
make stop

完成!

常见问题

我在网上找到的一些常见问题。请注意,答案大多是主观的。

问:这是实现整洁架构的正确方式吗?

答:不是。有很多方法可以实现整洁架构 - 这个示例是其中之一。有些项目可能比这个示例更好。

问:为什么认证是实现细节?

答:认证是实现细节,因为它不与用例或 repository/interface 层交互。认证有点特殊,可以作为中间件在其他任何路由中实现。请注意,这是我的主观意见。

问:这是推荐的 Fiber 项目结构方式吗?

答:不是。和其他 Gopher 一样,我建议您从一个单独的 main.go 文件开始您的项目。有些项目不需要复杂的架构。当您开始看到分支的需求时,我建议您根据功能职责拆分您的代码。如果您需要更严格的结构,那么您可以尝试采用整洁架构或您认为合适的任何其他架构,例如 Onion、Hexagonal 等等。

问:这只适用于 Fiber 吗?

答:不是。您可以简单地调整 handler.gomiddleware.go 文件,以将外部接口/presenters 和 drivers 层更改为其他内容。您可以使用 net/httpgin-gonicecho 等等。如果您想更改或添加数据库,您只需相应地调整 repository.go 文件。如果您想更改业务逻辑,只需更改 service.go 文件。只要职责分离做得好,您就不需要更改太多东西。

问:这可以用于生产环境吗?

答:我尽量让它尽可能适用于生产环境😉

改进

本项目可以实现的进一步改进:

  • 增加更多测试和 Mock,尤其是单元测试(整洁架构最适合执行单元测试)。
  • 增加更多 API 端点。
  • 在 repository 层添加缓存机制,例如 Redis。
  • 添加事务支持。
  • 也许尝试将 S3 后端集成到 repository 层(MinIO 是个不错的选择)。
  • 也许在 internal 包中添加一个 domain 文件夹,我们将实体放在那里?

讨论

请随时在此仓库中创建 Issue(或者在 Fiber 的 Discord 服务器上提问),以便我们一起讨论!

参考资料

感谢我阅读并从中获得灵感的文章及其作者!