When to use RESTful API
For most startups, we should focus more on delivering the products in the early stage of business. The monolithic services have the advantages of simple architecture, easy deployment, and better development productivity, which can help us achieve the product requirements quickly. While we use monolithic services to deliver products quickly, we also need to reserve the possibility for business incresement, so we usually split different business modules clearly in monolithic services.
Shopping mall monolithic service architecture
We take the mall as an example to build a monolithic service. The mall service is generally relatively complex and consists of multiple modules, the more important modules include account, product and order modules, etc. Each module will have its own independent business logic, and each module will also depend on some others. For example, the order module and the product module will depend on the account module. In the monolithic application this kind of dependency is usually accomplished by method calls between modules. Monolithic services generally share storage resources, such as MySQL and Redis.
The overall architecture of monolithic services is relatively simple, which is also the advantage of monolithic services. Customer requests are parsed through DNS and forwarded to the mall's backend services through Nginx. Mall services are deployed on cloud hosts. In order to achieve greater throughput and high availability, the service will generally deployed with multiple copies. This simple architecture can carry high throughput if well optimized.
For example, a request for order details interface /order/detail is routed to the order module, which relies on the account module and the product module to compose the complete order details back to the user, and multiple modules in a single service generally share the database and cache.
Monolithic Service
The next section describes how to quickly implement a mall monolithic service based on go-zero. Devs who have used go-zero know that we provide an API format file to describe the Restful API, and then we can generate the corresponding code by goctl with one command, we just need to fill in the corresponding business logic in the logic files. The mall service contains several modules, and in order to make the modules independent from each other, different modules are defined by separate APIs, but all the APIs are defined for the same service (mall-api).
Create user.api, order.api, product.api and mall.api in the api directory, where mall.api is the aggregated api file. Other api files are imported via import directives.
api
|-- mall.api
|-- order.api
|-- product.api
|-- user.api
Mall API Definition
mall.api is defined as follows, where syntax = "v1" means that this is the v1 syntax of zero-api.
syntax = "v1"
import "user.api"
import "order.api"
import "product.api"
Account module API definition
- View user details
- Get all orders for a user
user.api is defined as follows.
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
order.api is defined as follows.
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
product.api is defined as follows.
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)
}
Generating the monolithic service
With the API already defined, generating a service with the API becomes very simple, we use goctl to generate the monolithic service code.
$ goctl api go -api api/mall.api -dir .
The generated code is structured 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
Let's explain the generated files.
-
api: holds theAPIdescription file -
etc: used to define the project configuration, all configuration items can be written inmall-api.yaml -
internal/config: the configuration definition of the service -
internal/handler: the implementation of thehandlercorresponding to the routes defined in theAPIfile -
internal/logic: used to put the business logic corresponding to each route, the reason for the distinction betweenhandlerandlogicis to make the business processing part as less dependent as possible, to separateHTTP requestsfrom the logic processing code, and to facilitate the subsequent splitting intoRPC service -
internal/svc: used to define the dependencies of the business logic processing, we can create the dependent resources in themainfunction and pass them tohandlerandlogicviaServiceContext -
internal/types: defines theAPIrequest and response data structures -
mall.go: the file where themainfunction is located, with the same name as theservicein theAPIdefinition, minus the-apisuffix
The generated service can be run without any modification:
$ go run mall.go
Starting server at 0.0.0.0:8888...
Implementing the business logic
Next, let's implement the business logic. The logic will be simple for demonstration purposes, not real business logic.
First, let's implement the logic of getting all orders for users. Since there is no order-related information in the user module, we need to rely on the order module to query the orders of users, so we add a dependency on OrderLogic in UserOrdersLogic.
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 a method in OrderLogic to query all orders based on user 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
}
Call the ordersByUser method in the UserOrders method of UserOrdersLogic.
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 point we restart the mall-api service and request all the user's orders in the browser.
http://localhost:8888/user/123/orders
The return result is as follows, as we expected
{
"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 we'll implement the logic for creating an order. To create an order we first need to see if the item in stock is enough, so we need to rely on the item module in the order module.
type OrderCreateLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
productLogic *ProductLogic
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 product 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
}
The above shows that using go-zero to develop a monolithic service is very simple, which helps us to develop quickly. And we also separated modules, which also provides the possibility of changing to microservices later.
Summary
The above example shows that it is very simple to use go-zero to develop monolithic services. You only need to define the api file, and then the goctl tool can automatically generate the project code. We only need to fill in the business logic code in the logic package. In this article we just demonstrated how to quickly develop monolithic services based on go-zero, which does not involve databases. In fact, goctl can also generate CRUD and cache code with one command.
And for different business scenarios, customization can also be achieved through customizing templates. And customized templates can be shared within the team through remote git repositories, which can be very efficient for team collaboration.
Project address
https://github.com/zeromicro/go-zero
Welcome to use go-zero and star to support us!


Top comments (4)
Fantastic article!
What software did you use to create those diagram?
Thanks!
The tool is onemodel.app
Thanks Kevin!!
hi Kevin,
In user.api, the UserOrdersReply struct should be:
UserOrdersReply {
Orders []*OrderReply
json:"orders"}
btw It's better if you push all code to github, thanks