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 fileAPI
file, and then usegoctl
to automatically generate it. It would be strange to see this kind of problem at first, why not use a service likeOSS
? 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 throughOSS
, which I think is quite reasonable. -
go-zero
Some students in the community also asked how to download a file by defining aAPI
file and thengoctl
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 simplygo-zero
a serviceJWT
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:
-
syntax = “v1”
means this is thezero-api
v1
syntax of ---8d02988520572c9c4c0a4246c3ca56ed--- -
type DownloadRequest
defines the request format ofDownload
-
service file-api
defines the request route forDownload
Upload
Service Definition
Example requirements are as follows:
- Upload files via
/upload
path - Return the upload status through
json
, of whichcode
can be used to express richer scenes thanHTTP 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:
-
syntax = “v1”
means this is thezero-api
v1
syntax of ---07b2316cf7c6b98198e489cb6de15bfa--- -
type UploadResponse
defines the return format ofUpload
-
service file-api
defines the request route ofUpload
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:
- Whether it is
Download
orUpload
we add the prefixrequest
andresponse
when the data is not defined directly, such as 3Request
f17b7dRequest
orResponse
like this -
download.api
upload.api
service
file-api
service name
,并没有Usedownload-api
andupload-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:
- The defined structure cannot have the same name
- All files included
service name
must be the same
The outermostAPI
file can also contain partial definitions of the sameservice
, but we recommend keeping it symmetrical unless theseAPI
do belong to the parent level, such as withDownload
andUpload
belong to the same logical level, so they should not be defined infile.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 definedAPI
interface description file, no need to say more -
etc
Directory: This is used to place theyaml
configuration file, all configuration items can be written in thefile-api.yaml
file -
file.go
:main
file where the function is located, the file name is the same asservice
, and the suffix-api
removed. -
internal/config
Directory: Configuration Definitions for Services -
internal/handler
Directory:API
The route defined in the file corresponds tohandler
implementation -
internal/logic
Directory: used to put the business processing logic corresponding to each route, the reason to distinguishhandler
andlogic
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 intoRPC service
-
internal/svc
目录:用来定义业务逻辑处理的依赖,我们可以在main
e7ede21b508c7e653342c3faf4616b22---里面创建依赖的资源,ServiceContext
传递给handler
logic
-
internal/types
directory: definesAPI
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 takeResponseWriter
asio.Writer
and pass it to thelogic
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 passhttp.Request
to thelogic
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:
- Define the
API
file for each submodule, for example:download.api
andupload.api
- Define the general
API
file, eg:file.api
. Used toimport
theAPI
file of each submodule defined in step 1 - Generate single service framework code by
goctl api go
command - 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!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。