项目搭建
其实微服务框架的核心还是围绕 RPC 和 HTTP 通信构建的,提供了诸如服务发现、负载均衡、熔断、限流、跟踪、度量和配置管理等微服务必需的功能。比如利用.proto文件来定义服务接口和数据结构,然后使用相应的命令来自动生成 Go 的 gRPC 代码;通过.api文件和一些工具命令来生成 HTTP 服务的路由、请求和响应的处理代码。
go-zero 微服务实战系列(二、服务拆分)这篇文章详细的介绍了如何搭建一个微服务项目。下面是我对这篇文章的笔记补充和总结:
微服务拆分的补充
商城系统作为一个复杂的业务系统,包括商品管理、订单处理、用户管理、支付处理、库存管理、推荐和营销等多个方面。下面是根据业务职能和领域驱动设计(DDD)的界限上下文进行微服务划分的对比分析:
-
业务职能划分
按照业务职能划分,我们会根据每个职能的操作和职责来定义服务的边界。在商城系统中,可以进行如下划分:
-
商品服务:处理商品的上架、信息更新、分类、搜索等功能。 -
订单服务:管理订单的创建、修改、查询和状态跟踪。 -
用户服务:维护用户信息、权限、登录、注册等。 -
支付服务:处理支付请求、支付状态、退款等。 -
库存服务:管理库存数量、库存锁定、库存释放等。 -
推荐服务:基于用户行为和购买历史推荐商品。 -
营销服务:管理促销活动、优惠券等营销工具。
每个服务都是围绕特定的业务职能构建的,职责清晰,开发和维护的团队可以专注于特定的业务区域。
-
-
DDD 界限上下文划分
DDD 提倡根据业务的边界上下文来划分服务,这通常是围绕业务流程和领域模型来组织代码和数据库模式的。在 DDD 中,界限上下文是具有明确界限的特定职责区域,在这之内部署了一致性的模型和接口。以商城系统为例,可能的界限上下文包括:
-
产品目录上下文:包括商品的管理和展示,与其他上下文隔离,只关心商品的信息和分类。 -
销售上下文:涉及订单处理的全部流程,从购物车管理到订单完成,可能还会包括支付和库存的交互。 -
身份与访问管理上下文:处理用户的认证和鉴权,可能与订单服务交互以确定用户身份。 -
库存管理上下文:独立处理库存,它将关注库存的状态和与订单服务的交互。 -
支付上下文:处理所有支付流程,包括支付请求、验证和记录。 -
推荐上下文:从用户行为、购买历史等多个上下文中收集数据,提供个性化推荐。
在 DDD 中,服务的划分更加注重业务流程和领域逻辑的整体一致性,而不是单一的职能。它鼓励深入分析业务场景以发现领域模型,这些模型定义了实体、值对象、聚合根、领域事件等概念,并围绕这些模型构建服务。
-
-
对比
-
专业知识要求:DDD 的划分方式要求团队对业务有更深入的理解,而业务职能划分可能更直观、易于理解。 -
服务间的耦合:职能划分可能导致服务间的耦合度较高,因为它们通常是基于业务操作而不是业务流程来划分的。而 DDD 强调的是低耦合的上下文间关系。 -
数据一致性:DDD 的划分可能更好地处理数据一致性问题,因为它通过聚合根和事务边界来控制数据的一致性。 -
复杂性:按照 DDD 划分服务通常更复杂,需要更多的设计和模型发现工作,但最终可能提供更灵活和可扩展的系统架构。 -
组织结构:DDD 的界限上下文划分使得团队可以更好地围绕业务能力组织,这与康威定律(Conway’s Law)相呼应,即组织架构会影响最终的系统设计。
-
项目结构的总结
关于项目的整体结构,大致如下:
├── Dockerfile 定义应用程序的容器化配置文件。├── (apps)/(internal) 存放内部代码的目录。├── cmd 包含各个微服务的入口点,例如main.go文件。├── conf 一些全局配置。├── pkg 用于存放公共库和工具代码└── go.mod对于每一个微服务内部,apps 里面的目录结构大致如下:
├─app 对外的 BFF 服务,接受来自客户端的请求,暴露 HTTP 接口。├─product 商品微服务│ ├─admin 对内的服务,区别于 rpc,更多的是面向运营侧的且数据权限较高,通过隔离可带来更好的代码级别的安全,直接提供HTTP接口。│ │ ├─etc│ │ └─internal│ └─rpc 对内的微服务,仅接受来自内部其他微服务或者 BFF 的请求,暴露 gRPC 接口。│ ├─etc│ ├─internal│ ├─rpc│ └─rpcclient└─user 用户微服务 ├─admin │ ├─etc │ └─internal └─rpc ├─etc ├─internal ├─rpc └─rpcclient一些 goctl 命令总结
goctl api 命令
用法:
- goctl api [flags]
- goctl api [command]
flags 这里不做介绍,详细看一看 command 有哪些:
- dart 为提供的 api 文件生成 dart 文件
- doc 生成 doc 文件
- format 格式化 api 文件
- go 为提供的 api 文件生成 go 文件
- kt 为提供的 api 文件生成 kotlin 代码
- new 快速创建 api 服务
- plugin 自定义文件生成器
- ts 为提供的 api 文件生成 ts 文件
- validate 验证 api 文件
1. goctl api doc 命令
根据 api 文件生成 markdown 文档。这个 Markdown 文件通常包含了 API 的详细文档,如 API 的路径、方法、请求参数、响应结构等信息,以及可能的注释。具体用法是:goctl api doc [flags]。其中 flags 可以是--dir或--o。具体含义如下:
| 参数字段 | 参数类型 | 是否必填 | 默认值 | 参数说明 |
|---|---|---|---|---|
| dir | string | YES | 空字符串 | 后面的参数必须是.api文件所在目录的路径,这个目录的路径可以是相对路径或绝对路径 |
| o | string | NO | 当前的工作目录 | 指定 md 文件所在的目录的名字 |
2. goctl api format 命令
递归格式化指定目录下的 api 文件。具体用法:goctl api format [flags]。其中 flags 可以是--declare、--dir和--stdin。
| 参数字段 | 参数类型 | 是否必填 | 默认值 | 参数说明 |
|---|---|---|---|---|
| declare | boolean | NO | false | 是否检测上下文 |
| dir | string | YES | 空字符串 | 后面的参数必须是.api文件所在目录的路径,这个目录的路径可以是相对路径或绝对路径 |
| stdin | boolean | NO | false | 是否格式化终端输入的 api 内容 |
3. goctl api go 命令
根据 api 文件生成 Go HTTP 代码。具体用法:goctl api go [flags]。
| 参数字段 | 参数类型 | 是否必填 | 默认值 | 参数说明 |
|---|---|---|---|---|
| api | string | YES | 空字符串 | api 文件路径 |
| branch | string | NO | 空字符串 | 远程模板所在 git 分支名称,仅当 remote 有值时使用 |
| dir | string | NO | 当前工作目录 | 代码输出目录 |
| home | string | NO | ${HOME}/.goctl |
本地模板文件目录 |
| remote | string | NO | 空字符串 | 远程模板所在 git 仓库地址,当此字段传值时,优先级高于 home 字段值 |
| style | string | NO | gozero |
输出文件和目录的命名风格格式化符号,详情见 文件风格 |
4. goctl api new 命令
快速生成 Go HTTP 服务,开发者需要在终端指定服务名称参数,输出目录为当前工作目录。具体用法:goctl api new [flags] server-name。
| 参数字段 | 参数类型 | 是否必填 | 默认值 | 参数说明 |
|---|---|---|---|---|
| branch | string | NO | 空字符串 | 远程模板所在 git 分支名称,仅当 remote 有值时使用 |
| home | string | NO | ${HOME}/.goctl |
本地模板文件目录 |
| remote | string | NO | 空字符串 | 远程模板所在 git 仓库地址,当此字段传值时,优先级高于 home 字段值 |
| style | string | NO | gozero |
输出文件和目录的命名风格格式化符号,详情见 文件风格 |
5. goctl api plugin 命令
用于引用插件生成代码,开发者需要在终端指定插件名称、参数等信息。具体用法:goctl api plugin [flags]。
| 参数字段 | 参数类型 | 是否必填 | 默认值 | 参数说明 |
|---|---|---|---|---|
| api | string | YES | 空字符串 | api 文件路径 |
| dir | string | NO | 当前工作目录 | 指定了插件处理后的文件存放的位置 |
| plugin | string | YES | 空字符串 | 插件可执行文件所在路径,支持本地和 http 文件 |
| style | string | NO | gozero |
输出文件和目录的命名风格格式化符号,详情见 文件风格 |
6. goctl api validate 命令
校验 api 文件是否符合规范。具体用法:goctl api validate [flags]。
| 参数字段 | 参数类型 | 是否必填 | 默认值 | 参数说明 |
|---|---|---|---|---|
| api | string | YES | 空字符串 | api 文件路径 |
goctl rpc 命令
用法:
- goctl rpc [flags]
- goctl rpc [command]
flags 这里不做介绍,详细看一看 command 有哪些:
- new 生成 rpc 演示服务
- protoc 生成 grpc 代码
1. goctl rpc new 命令
快速生成一个 rpc 服务,其接收一个终端参数来指定服务名称。具体用法:goctl rpc new [flags] rpc-name。
| 参数字段 | 参数类型 | 是否必填 | 默认值 | 参数说明 |
|---|---|---|---|---|
| branch | string | NO | 空字符串 | 模板仓库分支,配合 --remote 使用 |
| home | string | NO | ~/.goctl |
模板仓库本地路径,优先级高于 --remote |
| idea | bool | NO | false | 仅 idea 插件用,终端请忽略此字段 |
| remote | string | NO | 空字符串 | 模板仓库远程路径 |
| style | string | NO | gozero | 文件命名风格,详情可参考 文件风格 |
2. goctl rpc protoc 命令
根据 protobufer 文件生成 rpc 服务。具体用法:goctl rpc protoc [flags]。
| 参数字段 | 参数类型 | 是否必填 | 默认值 | 参数说明 |
|---|---|---|---|---|
| branch | string | NO | 空字符串 | 模板仓库分支,配合 --remote 使用 |
| home | string | NO | ~/.goctl |
模板仓库本地路径,优先级高于 --remote |
| multiple | bool | NO | false | 是否生成多个 rpc 服务 |
| remote | string | NO | 空字符串 | 模板仓库远程路径 |
| style | string | NO | gozero | 输出目录的文件命名风格,详情可参考 文件风格 |
| zrpc_out | string | NO | 空字符串 | 生成 go-zero 框架专用的 RPC 服务代码的目录 |
除了上述参数外,还支持 protoc 指令的原生参数。
示例:goctl rpc protoc demo.proto --go_out=./pb --go_grpc_out=./pb --zrpc_out=.
- –go_out 与 --go_grpc_out 生成的最终目录必须一致。
- –go_out & --go_grpc_out 和 --zrpc_out 的生成的最终目录必须不为同一目录。
注意
goctl rpc protoc 指令生成 rpc 服务对 proto 有一些事项须知:
- proto 文件中如果有
import语句,goctl 不会对 import 的 proto 文件进行处理,需要自行手动处理。 - rpc service 中的请求体和响应体必须是当前 proto 文件中的 message,不能是 import 的 proto 文件中的 message。