Go快速開發(fā)一個RESTful API服務
何時使用單體 RESTful 服務
對于很多初創(chuàng)公司來說,業(yè)務的早期我們更應該關注于業(yè)務價值的交付,而單體服務具有架構簡單,部署簡單,開發(fā)成本低等優(yōu)點,可以幫助我們快速實現(xiàn)產品需求。我們在使用單體服務快速交付業(yè)務價值的同時,也需要為業(yè)務的發(fā)展預留可能性,所以我們一般會在單體服務中清晰的拆分不同的業(yè)務模塊。
商城單體 RESTful 服務
我們以商城為例來構建單體服務,商城服務一般來說相對復雜,會由多個模塊組成,比較重要的模塊包括賬號模塊、商品模塊和訂單模塊等,每個模塊會有自己獨立的業(yè)務邏輯,同時每個模塊間也會相互依賴,比如訂單模塊和商品模塊都會依賴賬號模塊,在單體應用中這種依賴關系一般是通過模塊間方法調用來完成。一般單體服務會共享存儲資源,比如 MySQL 和 Redis 等。
單體服務的整體架構比較簡單,這也是單體服務的優(yōu)點,客戶請求通過 DNS 解析后通過 Nginx 轉發(fā)到商城的后端服務,商城服務部署在 ECS 云主機上,為了實現(xiàn)更大的吞吐和高可用一般會部署多個副本,這樣一個簡單的平民架構如果優(yōu)化好的話也是可以承載較高的吞吐的。
商城服務內部多個模塊間存在依賴關系,比如請求訂單詳情接口 /order/detail,通過路由轉發(fā)到訂單模塊,訂單模塊會依賴賬號模塊和商品模塊組成完整的訂單詳情內容返回給用戶,在單體服務中多個模塊一般會共享數(shù)據(jù)庫和緩存。
單體服務實現(xiàn)
接下來介紹如何基于 go-zero 來快速實現(xiàn)商城單體服務。使用過 go-zero 的同學都知道,我們提供了一個 API 格式的文件來描述 Restful API,然后可以通過 goctl 一鍵生成對應的代碼,我們只需要在 logic 文件里填寫對應的業(yè)務邏輯即可。商城服務包含多個模塊,為了模塊間相互獨立,所以不同模塊由單獨的 API 定義,但是所有的 API 的定義都是在同一個 service (mall-api) 下。
在 api 目錄下分別創(chuàng)建 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 的實現(xiàn)
- internal/logic:用來放每個路由對應的業(yè)務邏輯,之所以區(qū)分 handler 和 logic 是為了讓業(yè)務處理部分盡可能減少依賴,把 HTTP requests 和邏輯處理代碼隔離開,便于后續(xù)拆分成 RPC service
- internal/svc:用來定義業(yè)務邏輯處理的依賴,我們可以在 main 函數(shù)里面創(chuàng)建依賴的資源,然后通過 ServiceContext 傳遞給 handler 和 logic
- internal/types:定義了 API 請求和返回數(shù)據(jù)結構
- mall.go:main 函數(shù)所在文件,文件名和 API 定義中的 service 同名,去掉了后綴 -api
生成的服務不需要做任何修改就可以運行:
$ go run mall.go Starting server at 0.0.0.0:8888...
實現(xiàn)業(yè)務邏輯
接下來我們來一起實現(xiàn)一下業(yè)務邏輯,出于演示目的邏輯會比較簡單,并非真正業(yè)務邏輯。
首先,我們先來實現(xiàn)用戶獲取所有訂單的邏輯,因為在用戶模塊并沒有訂單相關的信息,所以我們需要依賴訂單模塊查詢用戶的訂單,所以我們在 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 中實現(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 方法中調用 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" } ] }
接下來我們再來實現(xiàn)創(chuàng)建訂單的邏輯,創(chuàng)建訂單首先需要查看該商品的庫存是否足夠,所以在訂單模塊中需要依賴商品模塊。
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 開發(fā)單體服務還是非常簡單的,有助于我們快速開發(fā)上線,同時我們還做了模塊的劃分,為以后做微服務的拆分也打下了基礎。
總結
通過以上的示例可以看出使用 go-zero 實現(xiàn)單體服務非常簡單,只需要定義 api 文件,然后通過 goctl 工具就能自動生成項目代碼,我們只需要在logic中填寫業(yè)務邏輯即可,這里只是為了演示如何基于 go-zero 快速開發(fā)單體服務并沒有涉及數(shù)據(jù)庫和緩存的操作,其實我們的 goctl 也可以一鍵生成 CRUD 代碼和 cache 代碼,對于開發(fā)單體服務來說可以起到事半功倍的效果。
并且針對不同的業(yè)務場景,定制化的需求也可以通過自定義模板來實現(xiàn),還可以在團隊內通過遠程 git 倉庫共享自定義業(yè)務模板,可以很好的實現(xiàn)團隊協(xié)同。
以上就是Go快速開發(fā)一個RESTful API服務的詳細內容,更多關于Go開發(fā)RESTful API服務的資料請關注腳本之家其它相關文章!
相關文章
Go語言使用net/http實現(xiàn)簡單登錄驗證和文件上傳功能
這篇文章主要介紹了Go語言使用net/http實現(xiàn)簡單登錄驗證和文件上傳功能,使用net/http模塊編寫了一個簡單的登錄驗證和文件上傳的功能,在此做個簡單記錄,需要的朋友可以參考下2023-07-07