2

The Origin of Monolithic Best Practices

  • For many startups, in the early stage of the business, we should focus more on the delivery of business value, and the user volume at this time is also very small, QPS is also very low, we should use a simpler technical architecture to Accelerate the delivery of business value, and the advantages of a single unit are reflected at this time.
  • As I often mentioned when sharing live broadcasts, while we use the monomer to quickly deliver business value, we also need to reserve the possibility for the development of the business. We can clearly split the business modules in the monomer.
  • go-zero There are also many small partners in the community asking what the best practice of our single development should be.

And go-zero as a widely used progressive microservice framework, it was also precipitated by me in the complete development process of many large-scale projects. Naturally, we also fully considered the scenario of single service development.

As shown in the figure, the single structure using go-zero can also support a large scale of business, of which Service is a number of single services Pod .

Through this article, I will share with you in detail how to use go-zero to quickly develop a single service with multiple modules.

Monolith example

We use an upload and download single service to explain the best practice of single service development go-zero , why use such an example?

  • go-zero the community, students often ask how to define the uploaded file API file, and then use goctl to automatically generate it. It would be strange to see this kind of problem at first, why not use a service like OSS ? It is found that in many scenarios, the user needs to upload an excel, and then the server will discard the file after parsing. One is that the file is small, and the other is that the number of users is not large, so there is no need to go around it through OSS , which I think is quite reasonable.
  • go-zero Some students in the community also asked how to download a file by defining a API file and then goctl automatically generated. There are two reasons why this kind of problem is done through Go. One is that the business has just started, and it can be done by simply go-zero a service JWT Automatic authentication.

This is just an example, no need to delve into whether uploading and downloading should be implemented through Go . Then let's see how we can solve such a single service through go-zero , which we call a file service. The architecture is as follows:

Monolithic implementation

API definition

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. Let's take a look at download and upload service definition API .

Download Service Definition

Example requirements are as follows:

  • Download the file named <filename> through the /static/<filename> path
  • Just return the contents of the file

We create a file named download.api in the api directory with the following content:

 syntax = "v1"

type DownloadRequest {
  File string `path:"file"`
}

service file-api {
  @handler DownloadHandler
  get /static/:file(DownloadRequest)
}

The syntax of zero-api is relatively self-explanatory, with the following meanings:

  1. syntax = “v1” means this is the zero-api v1 syntax of ---8d02988520572c9c4c0a4246c3ca56ed---
  2. type DownloadRequest defines the request format of Download
  3. service file-api defines the request route for Download

Upload Service Definition

Example requirements are as follows:

  • Upload files via /upload path
  • Return the upload status through json , of which code can be used to express richer scenes than HTTP code

We create a file named upload.api in the api directory with the following content:

 syntax = "v1"

type UploadResponse {
  Code int `json:"code"`
}

service file-api {
  @handler UploadHandler
  post /upload returns (UploadResponse)
}

The explanation is as follows:

  1. syntax = “v1” means this is the zero-api v1 syntax of ---07b2316cf7c6b98198e489cb6de15bfa---
  2. type UploadResponse defines the return format of Upload
  3. service file-api defines the request route of Upload

Here comes the problem

Download and Upload we have defined services, so how can we put them into a service to provide services to users?

I don't know if you have noticed some details:

  1. Whether it is Download or Upload we add the prefix request and response when the data is not defined directly, such as 3 Request f17b7d Request or Response like this
  2. download.api upload.api service file-api service name ,并没有Use download-api and upload-api respectively

The purpose of this is actually to automatically generate the corresponding Go code by placing these two services in the same monomer. Let's see how to combine Download and Upload together~

Defining Monolithic Service Interfaces

For the sake of simplicity, goctl only supports accepting a single API file as a parameter, and accepts multiple API files at the same time. program, which may be supported in the future.

We create a new file.api file in the api directory with the following content:

 syntax = "v1"

import "download.api"
import "upload.api"

In this way, we import the #include Download and Upload services just like C/C++ 42fce54f99a090215cf99c2f1c9b631b---. But there are a few things to note:

  1. The defined structure cannot have the same name
  2. All files included service name must be the same
The outermost API file can also contain partial definitions of the same service , but we recommend keeping it symmetrical unless these API do belong to the parent level, such as with Download and Upload belong to the same logical level, so they should not be defined in file.api .

So far, our file structure is as follows:

 .
└── api
    ├── download.api
    ├── file.api
    └── upload.api

Generate a monolithic service

Now that we have the API interface definition, then for go-zero , the next thing is very simple and straightforward (of course, the definition API is also quite simple , isn't it?), let's use goctl to generate the monolithic service code.

 $ goctl api go -api api/file.api -dir .

Let's take a look at the generated file structure:

 .
├── api
│   ├── download.api
│   ├── file.api
│   └── upload.api
├── etc
│   └── file-api.yaml
├── file.go
├── go.mod
├── go.sum
└── internal
    ├── config
    │   └── config.go
    ├── handler
    │   ├── downloadhandler.go
    │   ├── routes.go
    │   └── uploadhandler.go
    ├── logic
    │   ├── downloadlogic.go
    │   └── uploadlogic.go
    ├── svc
    │   └── servicecontext.go
    └── types
        └── types.go

Let's explain the composition of the project code by directory:

  • api directory: we defined API interface description file, no need to say more
  • etc Directory: This is used to place the yaml configuration file, all configuration items can be written in the file-api.yaml file
  • file.go : main file where the function is located, the file name is the same as service , and the suffix -api removed.
  • internal/config Directory: Configuration Definitions for Services
  • internal/handler Directory: API The route defined in the file corresponds to handler implementation
  • internal/logic Directory: used to put the business processing logic corresponding to each route, the reason to distinguish handler and logic is to reduce the dependency of the business processing part as much as possible. HTTP requests from the logic processing code to facilitate subsequent splitting into RPC service
  • internal/svc目录:用来定义业务逻辑处理的依赖,我们可以在main e7ede21b508c7e653342c3faf4616b22---里面创建依赖的资源, ServiceContext传递给handler logic
  • internal/types directory: defines API request and return data structures

Let's not change anything, let's run and see the effect.

 $ go run file.go -f etc/file-api.yaml
Starting server at 0.0.0.0:8888...

Implement business logic

Next, we need to implement the relevant business logic, but the logic here is only for demonstration purposes. There is no need to pay too much attention to the implementation details. We only need to understand that we should write the business logic in the logic layer.

Here are a few things done:

  • Add the Path setting in the configuration item to place the uploaded file. The default value I wrote the current directory, because it is an example, as follows:
 type Config struct {
  rest.RestConf
  // 新增
  Path string `json:",default=."`
}
  • Adjusted the size limit of the request body as follows:
 Name: file-api
Host: localhost
Port: 8888
# 新增
MaxBytes: 1073741824
  • Since Download needs to write a file to the client, we take ResponseWriter as io.Writer and pass it to the logic modified code as follows--- :
 func (l *DownloadLogic) Download(req *types.DownloadRequest) error {
  logx.Infof("download %s", req.File)
  body, err := ioutil.ReadFile(req.File)
  if err != nil {
    return err
  }

  n, err := l.writer.Write(body)
  if err != nil {
    return err
  }

  if n < len(body) {
    return io.ErrClosedPipe
  }

  return nil
}
  • Since Upload needs to read the file uploaded by the user, we pass http.Request to the logic layer, and the modified code is as follows:
 func (l *UploadLogic) Upload() (resp *types.UploadResponse, err error) {
  l.r.ParseMultipartForm(maxFileSize)
  file, handler, err := l.r.FormFile("myFile")
  if err != nil {
    return nil, err
  }
  defer file.Close()

  logx.Infof("upload file: %+v, file size: %d, MIME header: %+v",
    handler.Filename, handler.Size, handler.Header)

  tempFile, err := os.Create(path.Join(l.svcCtx.Config.Path, handler.Filename))
  if err != nil {
    return nil, err
  }
  defer tempFile.Close()
  io.Copy(tempFile, file)

  return &types.UploadResponse{
    Code: 0,
  }, nil
}

Full code: https://github.com/zeromicro/zero-examples/tree/main/monolithic

We can start the file monolithic service by:

 $ go run file.go -f etc/file-api.yaml

It can be verified by curl Download service:

 $ curl -i "http://localhost:8888/static/file.go"
HTTP/1.1 200 OK
Traceparent: 00-831431c47d162b4decfb6b30fb232556-dd3b383feb1f13a9-00
Date: Mon, 25 Apr 2022 01:50:58 GMT
Content-Length: 584
Content-Type: text/plain; charset=utf-8

...

The example repository contains upload.html , and the browser can open this file and try the Upload service.

Summary of Monolithic Development

I summarize the complete process of developing a single service with go-zero as follows:

  1. Define the API file for each submodule, for example: download.api and upload.api
  2. Define the general API file, eg: file.api . Used to import the API file of each submodule defined in step 1
  3. Generate single service framework code by goctl api go command
  4. Add and adjust the configuration to realize the business logic of the corresponding sub-module

In addition, goctl can generate CRUD and cache code with one-key generation according to SQL , which can help you develop more c6a4ebce5 services more quickly.

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!


kevinwan
931 声望3.5k 粉丝

go-zero作者