When to use a monolithic RESTful service
For many startups, in the early stage of business, we should focus more on the delivery of business value, and the single service has the advantages of simple architecture, simple deployment, and low development cost, which can help us quickly realize product requirements. While we use the single service to quickly deliver business value, we also need to reserve the possibility for the development of the business, so we generally separate different business modules clearly in the single service.
Mall single RESTful service
We take the mall as an example to build a single service. Generally speaking, the mall service is relatively complex and consists of multiple modules. The more important modules include the account module, the commodity module, and the order module. Each module will have its own independent business. At the same time, each module will also depend on each other. For example, the order module and the commodity module will depend on the account module. In a monolithic application, this dependency is generally accomplished through method calls between modules. Generally, a single service will share storage resources, such as MySQL
and Redis
and so on.
The overall architecture of the single service is relatively simple, which is also the advantage of the single service. The customer request passes through DNS
after parsing and then passes through Nginx
is forwarded to the back-end service of the mall, and the mall service is deployed ECS
On cloud hosts, in order to achieve greater throughput and high availability, multiple copies are generally deployed. Such a simple 平民架构
can also carry higher throughput if optimized.
There are dependencies between multiple modules within the mall service, such as the request for order details interface /order/detail
, which is forwarded to the order module through routing. The order module will rely on the account module and the commodity module to form a complete order details and return it to the user. In a monolithic service, multiple modules typically share databases and caches.
Monolithic service implementation
Next, we will introduce how to quickly realize the single service of the mall based on go-zero
. Students who have used go-zero
know that we provide a API
format file to describe Restful API
, and then you can use the goctl
key To generate the corresponding code, we only need to fill in the corresponding business logic in the logic
file. The mall service contains multiple modules. In order to be independent of each other, different modules are defined by separate API
, but all API
are defined in the same service (mall-api)
down.
api
目录user.api
, order.api
, product.api
mall.api
,其中mall.api
the aggregated api
file, imported through import
, the file list is as follows:
api
|-- mall.api
|-- order.api
|-- product.api
|-- user.api
Mall API Definition
mall.api
is defined as follows, where syntax = “v1”
means this is the zero-api
6fabe3737a2d70ae8045042 syntax of v1
syntax = "v1"
import "user.api"
import "order.api"
import "product.api"
Account Module API Definition
- View user details
- Get all user orders
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)
}
Order Module API Definition
- Get order details
- Generate orders
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)
}
Product Module API Definition
- View product details
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)
}
Generate a monolithic service
It has been defined API
, and then use API
to generate the service will become very simple, we use goctl
to generate the single service code.
$ goctl api go -api api/mall.api -dir .
The generated code structure is as follows:
.
├── 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
Explain the structure of the generated code:
-
api
: storeAPI
description file -
etc
: used to define project configuration, all configuration items can be written inmall-api.yaml
-
internal/config
: Configuration definition of the service -
internal/handler
:API
---The implementation of the route defined in the file corresponds tohandler
-
internal/logic
: used to put the business logic corresponding to each route, the reason to distinguishhandler
andlogic
is to reduce the dependency of the business processing part as much as possibleHTTP requests
is isolated from the logic processing code, which is convenient for subsequent splitting intoRPC service
-
internal/svc
: used to define the dependencies of business logic processing, we can create dependent resources in themain
function, and then passServiceContext
tohandler
logic
-
internal/types
: definesAPI
request and return data structures -
mall.go
:main
函数所在文件,文件名API
定义中的service
,-api
The resulting service will run without any modification:
$ go run mall.go
Starting server at 0.0.0.0:8888...
Implement business logic
Next, let's implement the business logic together. For demonstration purposes, the logic will be relatively simple, not real business logic.
First of all, let's implement the logic that the user obtains all orders, because there is no order-related information in the user module, so we need to rely on the order module to query the user's orders OrderLogic
so we add the UserOrdersLogic
OrderLogic
depends
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),
}
}
Implement the method of querying all orders according to 用户id
OrderLogic
---425f9b86979b1e7f0ce702ba72c6b5fa---
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
the UserOrders
method in the ---71e730dc65b0bbd85718ea6f4d2ba5c5--- method of 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
}
At this time, we restart the mall-api
service and request to get all the user's order interface in the browser
http://localhost:8888/user/123/orders
The returned results are as follows, which is in line with our expectations
{
"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"
}
]
}
Next, let's implement the logic of creating an order. To create an order, we first need to check whether the inventory of the product is sufficient, so we need to rely on the product module in the order module.
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),
}
}
The logic for creating an order is as follows
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
}
The logic of the dependent commodity module is as follows
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
}
It can be seen from the above that it is very simple to use go-zero
to develop a single service, which will help us to quickly develop and go online. At the same time, we have also divided modules, which lays the foundation for the splitting of microservices in the future. .
Summarize
From the above example, it can be seen that using go-zero
to implement a single service is very simple, just need to define api
file, and then use the goctl
tool to automatically generate the project code , we only need to fill in the business logic in logic, here is just to demonstrate how to quickly develop a single service based on go-zero
does not involve database and cache operations, in fact, our goctl
You can also generate CRUD
code and cache
code with one click, which can achieve twice the result with half the effort for developing a single service.
And for different business scenarios, customized requirements can also be achieved through custom templates, and you can also share custom business templates within the team through a remote git
warehouse, which can well achieve team collaboration.
project address
https://github.com/zeromicro/go-zero
Welcome go-zero
and star support us!
WeChat exchange group
Follow the official account of " Microservice Practice " and click on the exchange group to get the QR code of the community group.
If you have go-zero
use experience articles, or source code study notes, please contact us through the public account to contribute!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。