Quickly develop a RESTful API service with Go

Time:2022-9-23

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 asMySQLandRedisWait.

The overall architecture of the single service is relatively simple, which is also the advantage of the single service.DNSpass after parsingNginxForwarded to the back-end service of the mall, the mall service is deployed inECSOn cloud hosts, in order to achieve greater throughput and high availability, multiple copies are generally deployed. Such a simpleCivilian ArchitectureIf optimized, it can also carry higher throughput.

There are dependencies between multiple modules within the mall service, such as requesting order details interface/order/detail, and 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 single service, multiple modules generally share the database and cache.

Monolithic service implementation

Next, we will introduce how to base ongo-zeroTo quickly realize the single service of the mall. usedgo-zerostudents know that we provide aAPIformat file to describeRestful API, which can then be accessed viagoctlOne-click to generate the corresponding code, we only need tologicFill in the corresponding business logic in the file. The mall service contains multiple modules. In order to be independent of each other, different modules are composed of separate modules.APIdefinition, but allAPIare defined in the sameservice (mall-api)Down.

existapiCreated separately in the directoryuser.api, order.api, product.apiandmall.api,inmall.apifor aggregatedapifile, viaimportImport, the file list is as follows:

api
|-- mall.api
|-- order.api
|-- product.api
|-- user.api

Mall API Definition

mall.apiis defined as follows, wheresyntax = “v1”means this iszero-apiofv1grammar

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

already definedAPI, then useAPIGenerating a service becomes very simple, we usegoctlGenerate monolithic 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: storageAPIdescription file
  • etc: used to define project configuration, all configuration items can be written inmall-api.yamlmiddle
  • internal/config: Configuration definition for the service
  • internal/handlerAPIThe routes defined in the file correspond tohandlerrealization
  • internal/logic: used to put the business logic corresponding to each route, the reason for the distinctionhandlerandlogicIn order to make the business processing part reduce dependencies as much as possible, putHTTP requestsIt is isolated from the logic processing code, which is convenient for subsequent splitting intoRPC service
  • internal/svc: used to define dependencies for business logic processing, we canmainCreate dependent resources in the function, and then passServiceContextPass tohandlerandlogic
  • internal/types:DefinedAPIRequest and return data structures
  • mall.gomainThe file where the function is located, the file name andAPIin the definitionserviceSame name, suffix removed-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, so we are inUserOrdersLogicadd pairOrderLogicrely

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),
    }
}

existOrderLogicimplementation based onuseridHow to query all orders

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
}

existUserOrdersLogicofUserOrdersmethod callordersByUsermethod

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 restartmall-apiService, 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 the use ofgo-zeroIt is very simple to develop a single service, which helps us to develop and launch quickly. At the same time, we also divide the modules, which also lays the foundation for the splitting of microservices in the future.

Summarize

From the above example, it can be seen that the use ofgo-zeroImplementing a single service is very simple, just defineapifile, then passgoctlThe tool can automatically generate the project code, we only need to fill in the business logic in the logic, here is just to demonstrate how togo-zeroRapid development of a single service does not involve database and cache operations. In fact, ourgoctlCan also be generated with one clickCRUDcode andcacheCode, for the development of a single service can have a multiplier effect.

And for different business scenarios, customized requirements can also be achieved through custom templates, and can also be implemented remotely within the team.gitThe warehouse shares custom business templates, which can well achieve team collaboration.

project address

https://github.com/zeromicro/go-zero

welcomego-zeroandstarSupport us!

WeChat exchange group

focus on”Microservice Practice』Public number and clickexchange groupGet the QR code of the community group.

if you havego-zeroThe use experience articles, or source code study notes, welcome to contact the submission through the public account!

Recommended Today

log4j2 exploit vulfocus-CVE-2021-44228

log4j2-CVE-2021-44228 The vulnerability environment is made in the vulfocus built by itself. After opening the exploit environment, visit the link.   Its injection location is a payload location in the /hello directory, and it is the data submitted by get. We directly access and click on these three question marks, and capture the package, we […]