Go快速開(kāi)發(fā)一個(gè)RESTful API服務(wù)
何時(shí)使用單體 RESTful 服務(wù)
對(duì)于很多初創(chuàng)公司來(lái)說(shuō),業(yè)務(wù)的早期我們更應(yīng)該關(guān)注于業(yè)務(wù)價(jià)值的交付,而單體服務(wù)具有架構(gòu)簡(jiǎn)單,部署簡(jiǎn)單,開(kāi)發(fā)成本低等優(yōu)點(diǎn),可以幫助我們快速實(shí)現(xiàn)產(chǎn)品需求。我們?cè)谑褂脝误w服務(wù)快速交付業(yè)務(wù)價(jià)值的同時(shí),也需要為業(yè)務(wù)的發(fā)展預(yù)留可能性,所以我們一般會(huì)在單體服務(wù)中清晰的拆分不同的業(yè)務(wù)模塊。
商城單體 RESTful 服務(wù)
我們以商城為例來(lái)構(gòu)建單體服務(wù),商城服務(wù)一般來(lái)說(shuō)相對(duì)復(fù)雜,會(huì)由多個(gè)模塊組成,比較重要的模塊包括賬號(hào)模塊、商品模塊和訂單模塊等,每個(gè)模塊會(huì)有自己獨(dú)立的業(yè)務(wù)邏輯,同時(shí)每個(gè)模塊間也會(huì)相互依賴,比如訂單模塊和商品模塊都會(huì)依賴賬號(hào)模塊,在單體應(yīng)用中這種依賴關(guān)系一般是通過(guò)模塊間方法調(diào)用來(lái)完成。一般單體服務(wù)會(huì)共享存儲(chǔ)資源,比如 MySQL 和 Redis 等。
單體服務(wù)的整體架構(gòu)比較簡(jiǎn)單,這也是單體服務(wù)的優(yōu)點(diǎn),客戶請(qǐng)求通過(guò) DNS 解析后通過(guò) Nginx 轉(zhuǎn)發(fā)到商城的后端服務(wù),商城服務(wù)部署在 ECS 云主機(jī)上,為了實(shí)現(xiàn)更大的吞吐和高可用一般會(huì)部署多個(gè)副本,這樣一個(gè)簡(jiǎn)單的平民架構(gòu)如果優(yōu)化好的話也是可以承載較高的吞吐的。
商城服務(wù)內(nèi)部多個(gè)模塊間存在依賴關(guān)系,比如請(qǐng)求訂單詳情接口 /order/detail,通過(guò)路由轉(zhuǎn)發(fā)到訂單模塊,訂單模塊會(huì)依賴賬號(hào)模塊和商品模塊組成完整的訂單詳情內(nèi)容返回給用戶,在單體服務(wù)中多個(gè)模塊一般會(huì)共享數(shù)據(jù)庫(kù)和緩存。
單體服務(wù)實(shí)現(xiàn)
接下來(lái)介紹如何基于 go-zero 來(lái)快速實(shí)現(xiàn)商城單體服務(wù)。使用過(guò) go-zero 的同學(xué)都知道,我們提供了一個(gè) API 格式的文件來(lái)描述 Restful API,然后可以通過(guò) goctl 一鍵生成對(duì)應(yīng)的代碼,我們只需要在 logic 文件里填寫(xiě)對(duì)應(yīng)的業(yè)務(wù)邏輯即可。商城服務(wù)包含多個(gè)模塊,為了模塊間相互獨(dú)立,所以不同模塊由單獨(dú)的 API 定義,但是所有的 API 的定義都是在同一個(gè) service (mall-api) 下。
在 api 目錄下分別創(chuàng)建 user.api, order.api, product.api 和 mall.api,其中 mall.api 為聚合的 api 文件,通過(guò) import 導(dǎo)入,文件列表如下:
api |-- mall.api |-- order.api |-- product.api |-- user.api
Mall API 定義
mall.api 的定義如下,其中 syntax = “v1” 表示這是 zero-api 的 v1 語(yǔ)法
syntax = "v1" import "user.api" import "order.api" import "product.api"
賬號(hào)模塊 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) }
生成單體服務(wù)
已經(jīng)定義好了 API,接下來(lái)用 API 生成服務(wù)就會(huì)變得非常簡(jiǎn)單,我們使用 goctl 生成單體服務(wù)代碼。
$ goctl api go -api api/mall.api -dir .
生成的代碼結(jié)構(gòu)如下:
. ├── 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
解釋一下生成的代碼結(jié)構(gòu):
- api:存放 API 描述文件
- etc:用來(lái)定義項(xiàng)目配置,所有的配置項(xiàng)都可以寫(xiě)在 mall-api.yaml 中
- internal/config:服務(wù)的配置定義
- internal/handler:API 文件中定義的路由對(duì)應(yīng)的 handler 的實(shí)現(xiàn)
- internal/logic:用來(lái)放每個(gè)路由對(duì)應(yīng)的業(yè)務(wù)邏輯,之所以區(qū)分 handler 和 logic 是為了讓業(yè)務(wù)處理部分盡可能減少依賴,把 HTTP requests 和邏輯處理代碼隔離開(kāi),便于后續(xù)拆分成 RPC service
- internal/svc:用來(lái)定義業(yè)務(wù)邏輯處理的依賴,我們可以在 main 函數(shù)里面創(chuàng)建依賴的資源,然后通過(guò) ServiceContext 傳遞給 handler 和 logic
- internal/types:定義了 API 請(qǐng)求和返回?cái)?shù)據(jù)結(jié)構(gòu)
- mall.go:main 函數(shù)所在文件,文件名和 API 定義中的 service 同名,去掉了后綴 -api
生成的服務(wù)不需要做任何修改就可以運(yùn)行:
$ go run mall.go Starting server at 0.0.0.0:8888...
實(shí)現(xiàn)業(yè)務(wù)邏輯
接下來(lái)我們來(lái)一起實(shí)現(xiàn)一下業(yè)務(wù)邏輯,出于演示目的邏輯會(huì)比較簡(jiǎn)單,并非真正業(yè)務(wù)邏輯。
首先,我們先來(lái)實(shí)現(xiàn)用戶獲取所有訂單的邏輯,因?yàn)樵谟脩裟K并沒(méi)有訂單相關(guān)的信息,所以我們需要依賴訂單模塊查詢用戶的訂單,所以我們?cè)?UserOrdersLogic 中添加對(duì) 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 中實(shí)現(xiàn)根據(jù) 用戶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 方法中調(diào)用 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 }
這時(shí)候我們重新啟動(dòng) mall-api 服務(wù),在瀏覽器中請(qǐng)求獲取用戶所有訂單接口
http://localhost:8888/user/123/orders
返回結(jié)果如下,符合我們的預(yù)期
{ "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" } ] }
接下來(lái)我們?cè)賮?lái)實(shí)現(xiàn)創(chuàng)建訂單的邏輯,創(chuàng)建訂單首先需要查看該商品的庫(kù)存是否足夠,所以在訂單模塊中需要依賴商品模塊。
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), } }
創(chuàng)建訂單的邏輯如下
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 開(kāi)發(fā)單體服務(wù)還是非常簡(jiǎn)單的,有助于我們快速開(kāi)發(fā)上線,同時(shí)我們還做了模塊的劃分,為以后做微服務(wù)的拆分也打下了基礎(chǔ)。
總結(jié)
通過(guò)以上的示例可以看出使用 go-zero 實(shí)現(xiàn)單體服務(wù)非常簡(jiǎn)單,只需要定義 api 文件,然后通過(guò) goctl 工具就能自動(dòng)生成項(xiàng)目代碼,我們只需要在logic中填寫(xiě)業(yè)務(wù)邏輯即可,這里只是為了演示如何基于 go-zero 快速開(kāi)發(fā)單體服務(wù)并沒(méi)有涉及數(shù)據(jù)庫(kù)和緩存的操作,其實(shí)我們的 goctl 也可以一鍵生成 CRUD 代碼和 cache 代碼,對(duì)于開(kāi)發(fā)單體服務(wù)來(lái)說(shuō)可以起到事半功倍的效果。
并且針對(duì)不同的業(yè)務(wù)場(chǎng)景,定制化的需求也可以通過(guò)自定義模板來(lái)實(shí)現(xiàn),還可以在團(tuán)隊(duì)內(nèi)通過(guò)遠(yuǎn)程 git 倉(cāng)庫(kù)共享自定義業(yè)務(wù)模板,可以很好的實(shí)現(xiàn)團(tuán)隊(duì)協(xié)同。
項(xiàng)目地址 github.com/zeromicro/g…
以上就是Go快速開(kāi)發(fā)一個(gè)RESTful API服務(wù)的詳細(xì)內(nèi)容,更多關(guān)于Go開(kāi)發(fā)RESTful API服務(wù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go定時(shí)器的三種實(shí)現(xiàn)方式示例詳解
這篇文章主要為大家介紹了Go定時(shí)器的三種實(shí)現(xiàn)方式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12HTTP服務(wù)壓力測(cè)試工具及相關(guān)術(shù)語(yǔ)講解
這篇文章主要為大家介紹了HTTP服務(wù)壓力測(cè)試工具使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04Go語(yǔ)言使用net/http實(shí)現(xiàn)簡(jiǎn)單登錄驗(yàn)證和文件上傳功能
這篇文章主要介紹了Go語(yǔ)言使用net/http實(shí)現(xiàn)簡(jiǎn)單登錄驗(yàn)證和文件上傳功能,使用net/http模塊編寫(xiě)了一個(gè)簡(jiǎn)單的登錄驗(yàn)證和文件上傳的功能,在此做個(gè)簡(jiǎn)單記錄,需要的朋友可以參考下2023-07-07Golang之sync.Pool對(duì)象池對(duì)象重用機(jī)制總結(jié)
這篇文章主要對(duì)Golang的sync.Pool對(duì)象池對(duì)象重用機(jī)制做了一個(gè)總結(jié),文中有相關(guān)的代碼示例和圖解,具有一定的參考價(jià)值,需要的朋友可以參考下2023-07-07完美解決golang go get私有倉(cāng)庫(kù)的問(wèn)題
這篇文章主要介紹了完美解決golang go get私有倉(cāng)庫(kù)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05go語(yǔ)言中json數(shù)據(jù)的讀取和寫(xiě)出操作
這篇文章主要介紹了go語(yǔ)言中json數(shù)據(jù)的讀取和寫(xiě)出操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04