该系列源码已开源:micro-shop
微服务架构是一种架构风格,它将一个大的系统构建为多个微服务的集合,这些微服务是围绕业务功能构建的,服务关注单一的业务功能,这些服务具有以下特点:
微服务架构能够快速、频繁、可靠地交付大型、复杂的应用程序,通过业务拆分实现服务组件化,使用组件进行组合从而快速开发系统。
在实际项目开发中,我们通常采用两种微服务划分策略:
DDD
界限上下文进行边界划分我们这里采用大家比较容易理解的业务职能的方式进行微服务划分,再次贴上我们电商项目的思维导图:
从以上思维导图可以看出,我们根据业务职能做如下微服务的划分:
product
) - 商品的添加、信息查询、库存管理等功能cart
) - 购物车的增删改查、收藏等功能order
) - 生成订单,订单管理等功能pay
) - 通过调用第三方支付实现支付等功能user
) - 用户信息、实名认证、账号设置、地址管理等功能home
) - 首页商品推荐、排行榜、限时开抢、banner等功能每个服务都可以再分为 api
服务和 rpc
服务。
api
服务对外,可提供给 app
调用。
rpc
服务是对内的,可提供给内部 api
服务或者其他 rpc
服务调用。
整个项目服务依赖流程图大致如下:
一般对于客户端我们会采用HTTP接口的方式提供服务,以上划分的这些微服务都需要直接提供HTTP接口对外提供服务,当然这样架构整体看起来也会比较简单。
但是这只是一个相对简单并且没有高并发的系统来。
对于一个复杂的高并发系统来讲,我们需要处理各种异常场景,比如:
某些页面需要依赖多个微服务提供数据,为了避免串行请求导致耗时过长。一般会采用并行请求多个微服务,这个时候其中某一个服务请求异常或宕机,那我们可能需要做一些特殊的处理,比如数据降级等
某些页面需要组合多个微服务的数据满足业务需求时,如果每个微服务都提供HTTP
对外接口,那这些复杂数据组合和异常处理都要在客户端完成,大家都知道客户端是不宜做复杂业务逻辑的,它的重点在与做交互体验,所以我们的架构组要做到前轻后重(即客户端逻辑尽量少而把比较重的业务处理逻辑下沉到服务端,而服务端又根据业务职能拆分成了不同的微服务,这些微服务只关注单一的业务
)
那么这些面向业务场景的复杂逻辑的处理应该放到哪里呢?
我们的解决方案就是加一层,即BFF层,通过BFF对外提供HTTP接口,客户端只与BFF进行交互。
BFF
层的引入解决了我们上面遇到的问题,但增加一层就会增加架构的复杂度,所以如果你的服务是一个单体应用的话,那么BFF是不必要的,引入它不会增加任何价值。
对于我们当前项目来说,应用程序依赖于微服务,同时需要面向业务功能提供HTTP
接口和要保证接口的高可用,所以BFF
对于当前项目来说是一个合适的选择。
BFF
在这里不做太多说明。具体可以自行百度搜索。
user
api服务 | 端口:9001 | rpc服务 | 端口:8080 |
---|---|---|---|
login | 用户登录接口 | login | 用户登录接口 |
register | 用户注册接口 | register | 用户注册接口 |
userinfo | 用户信息接口 | userinfo | 用户信息接口 |
… | … | … | … |
home
api服务 | 端口:9002 | rpc服务 | 端口:8081 |
---|---|---|---|
banner | 首页Banner接口 | banner | 首页Banner接口 |
recommend | 人气推荐接口 | recommend | 人气推荐接口 |
ranking-list | 排行榜接口 | ranking-list | 排行榜接口 |
… | … | … | … |
product
api服务 | 端口:9004 | rpc服务 | 端口:8083 |
---|---|---|---|
detail | 商品详情接口 | detail | 商品详情接口 |
lists | 商品列表接口 | lists | 商品列表接口 |
stock | 商品库存接口 | stock | 商品库存接口 |
cate-lists | 分类列表接口 | cate-lists | 分类列表接口 |
search | 搜索接口 | search | 搜索接口 |
… | … | … | … |
order
api服务 | 端口:9003 | rpc服务 | 端口:8082 |
---|---|---|---|
create-order | 创建订单接口 | create-order | 创建订单接口 |
order-detail | 订单详情接口 | order-detail | 订单详情接口 |
order-list | 订单列表接口 | order-list | 订单列表接口 |
order-cancel | 订单取消接口 | order-cancel | 订单取消接口 |
… | … | … | … |
pay
api服务 | 端口:9004 | rpc服务 | 端口:8083 |
---|---|---|---|
pay-order | 订单支付接口 | pay-order | 订单支付接口 |
pay-callback | 支付回调接口 | pay-callback | 支付回调接口 |
… | … | … | … |
cart
api服务 | 端口:9004 | rpc服务 | 端口:8083 |
---|---|---|---|
cart-add | 添加购物车接口 | cart-add | 添加购物车接口 |
cart-edit | 编辑购物车接口 | cart-edit | 编辑购物车接口 |
cart-del | 删除购物车接口 | cart-del | 删除购物车接口 |
cart-edit-num | 购物车数量变更接口 | cart-edit-num | 购物车数量变更接口 |
… | … | … | … |
microShop
├── cart // 购物车服务
│ ├── api
│ └── rpc
├── home // 首页服务
│ ├── api
│ └── rpc
├── order // 订单服务
│ ├── api
│ └── rpc
├── pay // 支付服务
│ ├── api
│ └── rpc
├── product // 产品服务
│ ├── api
│ └── rpc
└── user // 用户服务
├── api
└── rpc
...
...
以上就是咱们这次要实现服务的大体目录结构。
规划好服务目录结构后,咱么要进行开发离不来一个重要的环节:初始化
。go-zero
如何初始化呢?
还记得上一章节 4
小节说的吗? 如果不记得请看:go-zero 成长之路—微服务电商实战系列(一、需求篇) 第4
小节:需要使用到的命令和工具。
上一章节咱们提到过:goctl
,这个工具就是go-zero
用来快捷开发的一个服务生成工具。
具体的使用命令呢,可以去 go-zero
官方文档中查看。在这里咱们不过多介绍。
接下来咱们就来直接使用,以 user
服务为例:
API
服务,有两种构建方式:// 方式1:一键初始化api服务
goctl api new user
// 方式2:通过 编写的 user.api 文件进行生成, api.api 文件语法,参考 go-zero 官方文档
goctl api go -api user.api -dir . -style go-zero
构建后的目录结构如下:
├── api.api
├── etc
│ └── user-api.yaml
├── internal
│ ├── config
│ │ └── config.go
│ ├── handler
│ │ ├── handlers.go
│ │ └── routes.go
│ ├── logic
│ │ ├── login-logic.go
│ │ └── register-logic.go
│ ├── svc
│ │ └── service-context.go
│ └── types
│ └── types.go
├── model
│ ├── user_model.go
│ ├── user_model_gen.go
│ ├── user_receive_address_model.go
│ ├── user_receive_address_model_gen.go
│ └── vars.go
├── sql
│ └── user.sql
└── user.go
构建后怎么进行启动呢?通过go run
具体参考 go-zero
官方文档
go run user.go -f etc/user-api.yaml
RPC
服务:// 首先先创建 proto 文件
touch account.proto
// 书写proto文件配置, 具体参考 `go-zero` 官方文档
// 根据proto 文件生成RPC服务
goctl rpc protoc account.proto --go_out=./types --go-grpc_out=./types --zrpc_out=. -style go-zero
构建后的目录结构如下:
├── account.go
├── account.proto
├── etc
│ └── account.yaml
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ ├── login-logic.go
│ │ └── register-logic.go
│ ├── server
│ │ └── user-server.go
│ └── svc
│ └── service-context.go
├── model
│ ├── user_model.go
│ ├── user_model_gen.go
│ ├── user_receive_address_model.go
│ ├── user_receive_address_model_gen.go
│ └── vars.go
├── types
│ └── account
│ ├── account.pb.go
│ └── account_grpc.pb.go
└── user
└── user.go
启动方式 同上
go run account.go -f etc/account.yaml
model
模型,有两种构建方式,具体参考 go-zero
官方文档// 首先 先导出 sql 文件
// 其次通过sql文件的方式进行生成,也就是 ddl 方式
goctl model mysql ddl -src="*.sql" -dir="." -c -style go-zero
// 通过资源句柄链接方式进行生成
goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="*" -dir="./model"
到这里user
服务的代码初始化已经完成,其他服务和user
服务类似,这里就不再多说了。
本篇我们讲解了微服务的定义,微服务是围绕业务功能构建的,服务关注单一的业务,服务间采用轻量级的通讯机制,每个微服务都可以独立的部署和测试。
我们根据商城功能进行了微服务的拆分,主要拆分了购物车
、订单
、支付
、商品
、首页推荐
、用户
等服务,然后我们又说明了为什么需要引入BFF
服务,BFF
本质上是一个用于做数据组装的服务,对外提供面向业务功能的或者说面向客户端UI的HTTP
接口。
接着我们定义了我们这个工程的目录结构,主要分为api
、rpc
等服务,不同服务的职责不同,api
对外提供HTTP
接口,rpc
对内提供RPC
接口。
最后我们通过goctl
对项目做了初始化,使用goctl
可一键生成项目框架代码,大大提供了生产力。
希望本篇文章对你有所帮助,谢谢。