何时应用单体 RESTful 服务
对于很多初创公司来说,业务的晚期咱们更应该关注于业务价值的交付,而单体服务具备架构简略,部署简略,开发成本低等长处,能够帮忙咱们疾速实现产品需要。咱们在应用单体服务疾速交付业务价值的同时,也须要为业务的倒退预留可能性,所以咱们个别会在单体服务中清晰的拆分不同的业务模块。
商城单体 RESTful 服务
咱们以商城为例来构建单体服务,商城服务一般来说绝对简单,会由多个模块组成,比拟重要的模块包含账号模块、商品模块和订单模块等,每个模块会有本人独立的业务逻辑,同时每个模块间也会相互依赖,比方订单模块和商品模块都会依赖账号模块,在单体利用中这种依赖关系个别是通过模块间办法调用来实现。个别单体服务会共享存储资源,比方 MySQL
和 Redis
等。
单体服务的整体架构比较简单,这也是单体服务的长处,客户申请通过 DNS
解析后通过 Nginx
转发到商城的后端服务,商城服务部署在 ECS
云主机上,为了实现更大的吞吐和高可用个别会部署多个正本,这样一个简略的 平民架构
如果优化好的话也是能够承载较高的吞吐的。
商城服务外部多个模块间存在依赖关系,比方申请订单详情接口 /order/detail
,通过路由转发到订单模块,订单模块会依赖账号模块和商品模块组成残缺的订单详情内容返回给用户,在单体服务中多个模块个别会共享数据库和缓存。
单体服务实现
接下来介绍如何基于 go-zero
来疾速实现商城单体服务。应用过 go-zero
的同学都晓得,咱们提供了一个 API
格局的文件来形容 Restful API
,而后能够通过 goctl
一键生成对应的代码,咱们只须要在 logic
文件里填写对应的业务逻辑即可。商城服务蕴含多个模块,为了模块间互相独立,所以不同模块由独自的 API
定义,然而所有的 API
的定义都是在同一个 service (mall-api)
下。
在 api
目录下别离创立 user.api
, order.api
, product.api
和 mall.api
,其中 mall.api
为聚合的 api
文件,通过 import
导入,文件列表如下:
api
|-- mall.api
|-- order.api
|-- product.api
|-- user.api
Mall API 定义
mall.api
的定义如下,其中 syntax =“v1”
示意这是 zero-api
的 v1
语法
syntax = "v1"
import "user.api"
import "order.api"
import "product.api"
账号模块 API 定义
- 查看用户详情
- 获取用户所有订单
syntax = "v1"
type (
UserRequest {ID int64 `path:"id"`}
UserReply {
ID int64 `json:"id"`
Name string `json:"name"`
Balance float64 `json:"balance"`
}
UserOrdersRequest {ID int64 `path:"id"`}
UserOrdersReply {
ID string `json:"id"`
State uint32 `json:"state"`
CreateAt string `json:"create_at"`
}
)
service mall-api {
@handler UserHandler
get /user/:id (UserRequest) returns (UserReply)
@handler UserOrdersHandler
get /user/:id/orders (UserOrdersRequest) returns (UserOrdersReply)
}
订单模块 API 定义
- 获取订单详情
- 生成订单
syntax = "v1"
type (
OrderRequest {ID string `path:"id"`}
OrderReply {
ID string `json:"id"`
State uint32 `json:"state"`
CreateAt string `json:"create_at"`
}
OrderCreateRequest {ProductID int64 `json:"product_id"`}
OrderCreateReply {Code int `json:"code"`}
)
service mall-api {
@handler OrderHandler
get /order/:id (OrderRequest) returns (OrderReply)
@handler OrderCreateHandler
post /order/create (OrderCreateRequest) returns (OrderCreateReply)
}
商品模块 API 定义
- 查看商品详情
syntax = "v1"
type ProductRequest {ID int64 `path:"id"`}
type ProductReply {
ID int64 `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Count int64 `json:"count"`
}
service mall-api {
@handler ProductHandler
get /product/:id (ProductRequest) returns (ProductReply)
}
生成单体服务
曾经定义好了 API
,接下来用 API
生成服务就会变得非常简单,咱们应用 goctl
生成单体服务代码。
$ goctl api go -api api/mall.api -dir .
生成的代码构造如下:
.
├── api
│ ├── mall.api
│ ├── order.api
│ ├── product.api
│ └── user.api
├── etc
│ └── mall-api.yaml
├── internal
│ ├── config
│ │ └── config.go
│ ├── handler
│ │ ├── ordercreatehandler.go
│ │ ├── orderhandler.go
│ │ ├── producthandler.go
│ │ ├── routes.go
│ │ ├── userhandler.go
│ │ └── userordershandler.go
│ ├── logic
│ │ ├── ordercreatelogic.go
│ │ ├── orderlogic.go
│ │ ├── productlogic.go
│ │ ├── userlogic.go
│ │ └── userorderslogic.go
│ ├── svc
│ │ └── servicecontext.go
│ └── types
│ └── types.go
└── mall.go
解释一下生成的代码构造:
api
:寄存API
形容文件etc
:用来定义我的项目配置,所有的配置项都能够写在mall-api.yaml
中internal/config
:服务的配置定义internal/handler
:API
文件中定义的路由对应的handler
的实现internal/logic
:用来放每个路由对应的业务逻辑,之所以辨别handler
和logic
是为了让业务解决局部尽可能减少依赖,把HTTP requests
和逻辑解决代码隔离开,便于后续拆分成RPC service
internal/svc
:用来定义业务逻辑解决的依赖,咱们能够在main
函数外面创立依赖的资源,而后通过ServiceContext
传递给handler
和logic
internal/types
:定义了API
申请和返回数据结构mall.go
:main
函数所在文件,文件名和API
定义中的service
同名,去掉了后缀-api
生成的服务不须要做任何批改就能够运行:
$ go run mall.go
Starting server at 0.0.0.0:8888...
实现业务逻辑
接下来咱们来一起实现一下业务逻辑,出于演示目标逻辑会比较简单,并非真正业务逻辑。
首先,咱们先来实现用户获取所有订单的逻辑,因为在用户模块并没有订单相干的信息,所以咱们须要依赖订单模块查问用户的订单,所以咱们在 UserOrdersLogic
中增加对 OrderLogic
依赖
type UserOrdersLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
orderLogic *OrderLogic
}
func NewUserOrdersLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserOrdersLogic {
return &UserOrdersLogic{Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
orderLogic: NewOrderLogic(ctx, svcCtx),
}
}
在 OrderLogic
中实现依据 用户 id
查问所有订单的办法
func (l *OrderLogic) ordersByUser(uid int64) ([]*types.OrderReply, error) {
if uid == 123 {
// It should actually be queried from database or cache
return []*types.OrderReply{
{
ID: "236802838635",
State: 1,
CreateAt: "2022-5-12 22:59:59",
},
{
ID: "236802838636",
State: 1,
CreateAt: "2022-5-10 20:59:59",
},
}, nil
}
return nil, nil
}
在 UserOrdersLogic
的 UserOrders
办法中调用 ordersByUser
办法
func (l *UserOrdersLogic) UserOrders(req *types.UserOrdersRequest) (*types.UserOrdersReply, error) {orders, err := l.orderLogic.ordersByUser(req.ID)
if err != nil {return nil, err}
return &types.UserOrdersReply{Orders: orders,}, nil
}
这时候咱们重新启动 mall-api
服务,在浏览器中申请获取用户所有订单接口
http://localhost:8888/user/123/orders
返回后果如下,合乎咱们的预期
{
"orders": [
{
"id": "236802838635",
"state": 1,
"create_at": "2022-5-12 22:59:59"
},
{
"id": "236802838636",
"state": 1,
"create_at": "2022-5-10 20:59:59"
}
]
}
接下来咱们再来实现创立订单的逻辑,创立订单首先须要查看该商品的库存是否足够,所以在订单模块中须要依赖商品模块。
type OrderCreateLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
productLogic *ProductLogic
}
func NewOrderCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OrderCreateLogic {
return &OrderCreateLogic{Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
productLogic: NewProductLogic(ctx, svcCtx),
}
}
创立订单的逻辑如下
const (
success = 0
failure = -1
)
func (l *OrderCreateLogic) OrderCreate(req *types.OrderCreateRequest) (*types.OrderCreateReply, error) {product, err := l.productLogic.productByID(req.ProductID)
if err != nil {return nil, err}
if product.Count > 0 {return &types.OrderCreateReply{Code: success}, nil
}
return &types.OrderCreateReply{Code: failure}, nil
}
依赖的商品模块逻辑如下
func (l *ProductLogic) Product(req *types.ProductRequest) (*types.ProductReply, error) {return l.productByID(req.ID)
}
func (l *ProductLogic) productByID(id int64) (*types.ProductReply, error) {
return &types.ProductReply{
ID: id,
Name: "apple watch 3",
Price: 3333.33,
Count: 99,
}, nil
}
以上能够看出应用 go-zero
开发单体服务还是非常简单的,有助于咱们疾速开发上线,同时咱们还做了模块的划分,为当前做微服务的拆分也打下了根底。
总结
通过以上的示例能够看出应用 go-zero
实现单体服务非常简单,只须要定义 api
文件,而后通过 goctl
工具就能主动生成我的项目代码,咱们只须要在 logic 中填写业务逻辑即可,这里只是为了演示如何基于 go-zero
疾速开发单体服务并没有波及数据库和缓存的操作,其实咱们的 goctl
也能够一键生成 CRUD
代码和 cache
代码,对于开发单体服务来说能够起到事倍功半的成果。
并且针对不同的业务场景,定制化的需要也能够通过自定义模板来实现,还能够在团队内通过近程 git
仓库共享自定义业务模板,能够很好的实现团队协同。
我的项目地址
https://github.com/zeromicro/go-zero
欢送应用 go-zero
并 star 反对咱们!
微信交换群
关注『微服务实际 』公众号并点击 交换群 获取社区群二维码。
如果你有 go-zero
的应用心得文章,或者源码学习笔记,欢送通过公众号分割投稿!