type
status
date
slug
summary
tags
category
icon
password
Sub-item
Last edited time
Mar 24, 2024 03:12 AM
Parent item
领域
该电子书通过实际案例的方式,介绍了在项目中如何使用DDD和CQRS实践整洁架构,并对一些常见反模式的设计提出了改进方法,最终完成一个用 Golang 构建的面向业务的应用程序框架。
本书的想法是不要过多关注基础设施和实现细节,在介绍具体的方法之前,需要先设计一个便于举例的模拟项目案例,本文就是介绍该模拟项目的框架和背景。
基础设施:使用Google Cloud托管K8S集群
考虑到使用自建K8S集群部署需要一个专门的DevOps团队,成本较高,因此采用托管K8S集群到云上的方式更加灵活方便, 你只需要提供容器镜像即可。在此容器内,您可以运行用任何语言编写的应用程序,该应用程序可以使用 HTTP 或 gRPC API 公开端口。也可以在此容器内处理 Pub/Sub 消息。这就是您在基础设施方面所需要的一切。本书创建了一个功能齐全、真实的应用程序,使用
Terraform
通过一条命令将此应用程序部署到 Google Cloud。当然,也可以通过
docker-compose
将应用部署到本地。基础设施:本地运行
对于由数百个微服务构建的项目来说,实现这一目标要困难得多。幸运的是,我们的项目 Wild Workouts 只有 5 个服务。在 Wild Workouts 中,我们创建了 Docker Compose,并支持热加载。
- 对于前端,我们使用带有
vue-cli-service
服务工具的容器。
- 对于后端来说,情况稍微复杂一些。在所有容器中,我们运行
reflex
工具。reflex
侦听任何触发服务重新编译的代码更改。
运行步骤
- 安装
docker
和docker-compose
- 下载代码:
git clone
https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example.git
&& cd wild-workouts-go-ddd-example
- 运行:
docker-compose up
- 访问:
http://localhost:8080/
。 这里官方也部署了一个可以公开访问的示例可以直接访问: https://threedotslabs-wildworkouts.web.app
示例项目Wild Workouts是做什么的
很多Go教程中的举得例子在实际项目中几乎没什么用。为了避免这个问题,我们创建了 Wild Workouts 应用程序作为一个功能齐全的项目。只有一开始花足够的时间,才能在后期的实施过程中节省更多的时间。
闲话少说,进入正题。Wild Workouts 是一款面向私人健身教练和学员的应用程序。
- 教练可以在他们有空的时间段里制定训练时间表
- 学员可以在安排的指定日期内训练
- 其他功能包括:
- “积分”管理(学员可以安排多少次培训)
- 取消 – 如果训练在开始前 24 小时内取消,学员将不会收到返还的积分,如果教练取消,那么额外赠送一节课作为补偿
- 培训重新安排
- 如果有人想要在培训开始前 24 小时内重新安排培训,则需要得到第二位参与者(教练或学员)的批准
- 日历视图
前端技术
前端和项目设计关系不大,不需要特别关注,这里用到的前端技术有:
- OpenAPI (Swagger) client。生成后端接口的说明文档的工具,方便JavaScript访问
- Bootstrap。前端资源打包工具,创建HTML等
- Vue.js。前端开发框架
后端服务
- trainer服务。管理教练的时间表,提供公开的HTTP接口,和内部gRPC接口
- trainings服务。管理学员的培训。提供公开的HTTP接口
- users服务。管理积分和用户信息
公开HTTP接口
应用程序执行的大多数操作都是由 HTTP API 触发的。我多次听到 Go 新手问他们应该使用什么框架来创建 HTTP 服务。我始终建议不要在 Go 中使用任何类型的 HTTP 框架。一个简单的路由器,比如 chi 就足够了。 chi 只为我们提供了轻量级的粘合剂来定义我们的 API 支持哪些 URL 和方法。在底层,它使用 Go 标准库 http 包,因此所有相关工具(如中间件)都是 100% 兼容的。在 Go 中使用任何类型的框架都会增加不必要的复杂性,并将您的项目与该框架耦合在一起,这里需要遵循KISS原则(Keep It Simple And Stupid)。所有服务都以相同的方式运行 HTTP 服务器。
译注:这里我们使用gin框架也是没有问题的。
然后在trainings服务中启动:
RunHTTPServer
的入参是createHandler
函数,createHandler
需要返回http.Handler
。在我们的例子中,它是由 oapi-codegen
生成的 HandlerFromMux
(在openapi_api.gen.go)。它为我们提供了 OpenAPI 规范中的所有路径和查询参数接口定义在trainings.yml:
如果需要修改接口,需要重新生成GO和JavaScript客户端的代码,已经封装在make命令中:
make openapi
。工具不仅生成路由,还生成了业务功能接口:
下面是实现该接口的例子:
HTTP 路径并不是 OpenAPI 规范生成的唯一内容。更重要的是,它还为我们提供了响应和请求的模型。在大多数情况下,模型比 API 路径和方法复杂得多。生成它们可以在任何 API 约定更改期间节省时间。
举例,定义的数据(trainings.yml)如下:
生成的结构体如下:
云数据库Firestore
如果我们想以最现代、可扩展且真正无服务器的方式构建应用程序,Firestore 是一个自然的选择。我们将开箱即用。他的限制是1 update / second / one document. 但对于独立的文档是并行处理的,实际使用时问题不大。
在本地运行 Firestore
不幸的是 Firestore 模拟器并不完美。我发现有些情况下模拟器与真实版本不100%兼容。我也遇到过一些情况,当我在事务中更新和读取同一个文档时,它导致了死锁。在我看来,这个功能对于本地开发来说已经足够了。另一种选择可能是拥有一个单独的 Google Cloud 项目来进行本地开发。我在这里的偏好是拥有一个真正本地的本地环境,并且不依赖于任何外部服务。它也更容易设置,并且可以在以后的持续集成中使用。自 5 月底以来,Firestore 模拟器提供了 UI。它已添加到 Docker Compose 中,并可从 http://localhost:4000/ 获取。
使用Firestore
除了 Firestore 实现之外,代码在本地和生产环境中的工作方式相同。当我们在本地使用模拟器时,我们需要在.env文件中将 env
FIRESTORE_EMULATOR_HOST
设置为模拟器(firestore:8787)来运行我们的应用程序使用firestore:
读出来的数据都是map[string]interface,再转换为需要的数据格式
生产环境部署
再输入一些项目信息和账户信息就可以部署了。
《Go With The Domain》电子书下载: