单体最佳实践的由来

QPSgo-zero
go-zero
go-zeroServicePod
go-zero

单体示例

go-zero
go-zeroAPIgoctlOSSOSSgo-zeroAPIgoctlgo-zeroJWT
Gogo-zero

单体实现

API
go-zeroAPIRESTful APIgoctllogicdownloaduploadAPI
Download

示例需求如下:

/static/
apidownload.api
syntax = "v1"

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

service file-api {
  @handler DownloadHandler
  get /static/:file(DownloadRequest)
}
zero-api
syntax = “v1”zero-apiv1type DownloadRequestDownloadservice file-apiDownload
Upload

示例需求如下:

/uploadjsoncodeHTTP code
apiupload.api
syntax = "v1"

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

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

解释如下:

syntax = “v1”zero-apiv1type UploadResponseUploadservice file-apiUpload

问题来了

DownloadUpload

不知道细心的你有没注意到一些细节:

DownloadUploadrequestresponseRequestResponsedownload.apiupload.apiservicefile-apiservice namedownload-apiupload-api
GoDownloadUpload

定义单体服务接口

goctlAPIAPI
apifile.api
syntax = "v1"

import "download.api"
import "upload.api"
C/C++#includeDownloadUpload
service name
APIserviceAPIDownloadUploadfile.api

至此,我们的文件结构如下:

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

生成单体服务

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

我们来看看生成后的文件结构:

.
├── 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

我们来按目录解释一下项目代码的构成:

apiAPIetcyamlfile-api.yamlfile.gomainservice-apiinternal/configinternal/handlerAPIhandlerinternal/logichandlerlogicHTTP requestsRPC serviceinternal/svcmainServiceContexthandlerlogicinternal/typesAPI

咱们什么也不改,先来跑一下看看效果。

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

实现业务逻辑

logic

这里一共做了以下几件事:

Path
type Config struct {
  rest.RestConf
  // 新增
  Path string `json:",default=."`
}
  • 调整了请求体的大小限制,如下:

Name: file-api
Host: localhost
Port: 8888
# 新增
MaxBytes: 1073741824
DownloadResponseWriterio.Writerlogic
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
}
Uploadhttp.Requestlogic
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
}
file
$ go run file.go -f etc/file-api.yaml
curlDownload
$ 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

...
upload.htmlUpload

单体开发的总结

go-zero
APIdownload.apiupload.apiAPIfile.apiimportAPIgoctl api go
goctlSQLCRUDcache

项目地址

go-zero

微信交流群

关注『微服务实践』公众号并点击 交流群 获取社区群二维码。