From 11dd3d75ecceaa3f5772024fb3f26dec1ada8e9c Mon Sep 17 00:00:00 2001 From: kevin Date: Thu, 3 Sep 2020 23:26:04 +0800 Subject: [PATCH] add bookstore example --- doc/bookstore.md | 199 ++++------ doc/images/bookstore-api.png | Bin 0 -> 106410 bytes doc/images/bookstore-model.png | Bin 0 -> 79098 bytes doc/images/bookstore-rpc.png | Bin 0 -> 93901 bytes doc/shorturl.md | 2 +- example/bookstore/api/bookstore.api | 33 ++ example/bookstore/api/bookstore.go | 27 ++ example/bookstore/api/etc/bookstore-api.yaml | 13 + .../bookstore/api/internal/config/config.go | 12 + .../api/internal/handler/addhandler.go | 28 ++ .../api/internal/handler/checkhandler.go | 28 ++ .../bookstore/api/internal/handler/routes.go | 24 ++ .../bookstore/api/internal/logic/addlogic.go | 38 ++ .../api/internal/logic/checklogic.go | 38 ++ .../api/internal/svc/servicecontext.go | 23 ++ example/bookstore/api/internal/types/types.go | 20 + example/bookstore/go.mod | 11 + example/bookstore/go.sum | 356 ++++++++++++++++++ example/bookstore/rpc/add/add.go | 39 ++ example/bookstore/rpc/add/add.proto | 16 + example/bookstore/rpc/add/adder/adder.go | 62 +++ example/bookstore/rpc/add/adder/adder_mock.go | 49 +++ example/bookstore/rpc/add/adder/types.go | 19 + example/bookstore/rpc/add/etc/add.yaml | 10 + .../rpc/add/internal/config/config.go | 13 + .../rpc/add/internal/logic/addlogic.go | 38 ++ .../rpc/add/internal/server/adderserver.go | 26 ++ .../rpc/add/internal/svc/servicecontext.go | 20 + example/bookstore/rpc/add/pb/add.pb.go | 167 ++++++++ example/bookstore/rpc/check/check.go | 39 ++ example/bookstore/rpc/check/check.proto | 16 + .../bookstore/rpc/check/checker/checker.go | 62 +++ .../rpc/check/checker/checker_mock.go | 49 +++ example/bookstore/rpc/check/checker/types.go | 19 + example/bookstore/rpc/check/etc/check.yaml | 10 + .../rpc/check/internal/config/config.go | 13 + .../rpc/check/internal/logic/checklogic.go | 35 ++ .../check/internal/server/checkerserver.go | 26 ++ .../rpc/check/internal/svc/servicecontext.go | 20 + example/bookstore/rpc/check/pb/check.pb.go | 167 ++++++++ example/bookstore/rpc/model/book.sql | 6 + example/bookstore/rpc/model/bookmodel.go | 82 ++++ example/bookstore/rpc/model/vars.go | 5 + readme.md | 2 +- 44 files changed, 1741 insertions(+), 121 deletions(-) create mode 100644 doc/images/bookstore-api.png create mode 100644 doc/images/bookstore-model.png create mode 100644 doc/images/bookstore-rpc.png create mode 100644 example/bookstore/api/bookstore.api create mode 100644 example/bookstore/api/bookstore.go create mode 100644 example/bookstore/api/etc/bookstore-api.yaml create mode 100644 example/bookstore/api/internal/config/config.go create mode 100644 example/bookstore/api/internal/handler/addhandler.go create mode 100644 example/bookstore/api/internal/handler/checkhandler.go create mode 100644 example/bookstore/api/internal/handler/routes.go create mode 100644 example/bookstore/api/internal/logic/addlogic.go create mode 100644 example/bookstore/api/internal/logic/checklogic.go create mode 100644 example/bookstore/api/internal/svc/servicecontext.go create mode 100644 example/bookstore/api/internal/types/types.go create mode 100644 example/bookstore/go.mod create mode 100644 example/bookstore/go.sum create mode 100755 example/bookstore/rpc/add/add.go create mode 100755 example/bookstore/rpc/add/add.proto create mode 100755 example/bookstore/rpc/add/adder/adder.go create mode 100644 example/bookstore/rpc/add/adder/adder_mock.go create mode 100755 example/bookstore/rpc/add/adder/types.go create mode 100755 example/bookstore/rpc/add/etc/add.yaml create mode 100755 example/bookstore/rpc/add/internal/config/config.go create mode 100755 example/bookstore/rpc/add/internal/logic/addlogic.go create mode 100755 example/bookstore/rpc/add/internal/server/adderserver.go create mode 100755 example/bookstore/rpc/add/internal/svc/servicecontext.go create mode 100644 example/bookstore/rpc/add/pb/add.pb.go create mode 100755 example/bookstore/rpc/check/check.go create mode 100755 example/bookstore/rpc/check/check.proto create mode 100755 example/bookstore/rpc/check/checker/checker.go create mode 100644 example/bookstore/rpc/check/checker/checker_mock.go create mode 100755 example/bookstore/rpc/check/checker/types.go create mode 100755 example/bookstore/rpc/check/etc/check.yaml create mode 100755 example/bookstore/rpc/check/internal/config/config.go create mode 100755 example/bookstore/rpc/check/internal/logic/checklogic.go create mode 100755 example/bookstore/rpc/check/internal/server/checkerserver.go create mode 100755 example/bookstore/rpc/check/internal/svc/servicecontext.go create mode 100644 example/bookstore/rpc/check/pb/check.pb.go create mode 100644 example/bookstore/rpc/model/book.sql create mode 100755 example/bookstore/rpc/model/bookmodel.go create mode 100755 example/bookstore/rpc/model/vars.go diff --git a/doc/bookstore.md b/doc/bookstore.md index deb282f5..1a63f99d 100644 --- a/doc/bookstore.md +++ b/doc/bookstore.md @@ -39,15 +39,15 @@ * API Gateway - api + api * RPC - 架构图 + 架构图 * model - model + model 下面我们来一起完整走一遍快速构建微服务的流程,Let’s `Go`!🏃‍♂️ @@ -58,7 +58,7 @@ * 安装goctl工具 ```shell - GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get github.com/tal-tech/go-zero/tools/goctl + GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl ``` * 创建工作目录`bookstore` @@ -131,19 +131,19 @@ ├── bookstore.api // api定义 ├── bookstore.go // main入口定义 ├── etc - │   └── bookstore-api.yaml // 配置文件 + │ └── bookstore-api.yaml // 配置文件 └── internal ├── config - │   └── config.go // 定义配置 + │ └── config.go // 定义配置 ├── handler - │   ├── addhandler.go // 实现addHandler - │   ├── checkhandler.go // 实现checkHandler - │   └── routes.go // 定义路由处理 + │ ├── addhandler.go // 实现addHandler + │ ├── checkhandler.go // 实现checkHandler + │ └── routes.go // 定义路由处理 ├── logic - │   ├── addlogic.go // 实现AddLogic - │   └── checklogic.go // 实现CheckLogic + │ ├── addlogic.go // 实现AddLogic + │ └── checklogic.go // 实现CheckLogic ├── svc - │   └── servicecontext.go // 定义ServiceContext + │ └── servicecontext.go // 定义ServiceContext └── types └── types.go // 定义请求、返回结构体 ``` @@ -225,97 +225,35 @@ ├── add.go // rpc服务main函数 ├── add.proto // rpc接口定义 ├── adder - │   ├── adder.go // 提供了外部调用方法,无需修改 - │   ├── adder_mock.go // mock方法,测试用 - │   └── types.go // request/response结构体定义 + │ ├── adder.go // 提供了外部调用方法,无需修改 + │ ├── adder_mock.go // mock方法,测试用 + │ └── types.go // request/response结构体定义 ├── etc - │   └── add.yaml // 配置文件 + │ └── add.yaml // 配置文件 ├── internal - │   ├── config - │   │   └── config.go // 配置定义 - │   ├── logic - │   │   └── addlogic.go // add业务逻辑在这里实现 - │   ├── server - │   │   └── adderserver.go // 调用入口, 不需要修改 - │   └── svc - │   └── servicecontext.go // 定义ServiceContext,传递依赖 + │ ├── config + │ │ └── config.go // 配置定义 + │ ├── logic + │ │ └── addlogic.go // add业务逻辑在这里实现 + │ ├── server + │ │ └── adderserver.go // 调用入口, 不需要修改 + │ └── svc + │ └── servicecontext.go // 定义ServiceContext,传递依赖 └── pb └── add.pb.go ``` + 直接可以运行,如下: - + ```shell $ go run add.go -f etc/add.yaml Starting rpc server at 127.0.0.1:8080... - ``` - -`etc/add.yaml`文件里可以修改侦听端口等配置 - -## 7. 修改API Gateway代码调用add rpc服务 - -* 修改配置文件`bookstore-api.yaml`,增加如下内容 - - ```yaml - Add: - Etcd: - Hosts: - - localhost:2379 - Key: add.rpc - ``` - - 通过etcd自动去发现可用的add服务 - -* 修改`internal/config/config.go`如下,增加add服务依赖 - - ```go - type Config struct { - rest.RestConf - Add rpcx.RpcClientConf // 手动代码 - } - ``` - -* 修改`internal/svc/servicecontext.go`,如下: - - ```go - type ServiceContext struct { - Config config.Config - Adder adder.Adder // 手动代码 - } - - func NewServiceContext(c config.Config) *ServiceContext { - return &ServiceContext{ - Config: c, - Adder: adder.NewAdder(rpcx.MustNewClient(c.Add)), // 手动代码 - } - } - ``` - - 通过ServiceContext在不同业务逻辑之间传递依赖 - -* 修改`internal/logic/addlogic.go`里的`Add`方法,如下: - - ```go - func (l *AddLogic) Add(req types.AddReq) (*types.AddResp, error) { - // 手动代码开始 - resp, err := l.svcCtx.Adder.Add(l.ctx, &adder.AddReq{ - Book: req.Book, - Price: req.Price, - }) - if err != nil { - return nil, err - } - - return &types.AddResp{ - Ok: resp.Ok, - }, nil - // 手动代码结束 - } - ``` +``` - 通过调用`adder`的`Add`方法实现添加图书到bookstore系统 +`etc/add.yaml`文件里可以修改侦听端口等配置 -## 8. 编写check rpc服务 +## 7. 编写check rpc服务 * 在`rpc/check`目录下编写`check.proto`文件 @@ -359,20 +297,20 @@ ├── check.go // rpc服务main函数 ├── check.proto // rpc接口定义 ├── checker - │   ├── checker.go // 提供了外部调用方法,无需修改 - │   ├── checker_mock.go // mock方法,测试用 - │   └── types.go // request/response结构体定义 + │ ├── checker.go // 提供了外部调用方法,无需修改 + │ ├── checker_mock.go // mock方法,测试用 + │ └── types.go // request/response结构体定义 ├── etc - │   └── check.yaml // 配置文件 + │ └── check.yaml // 配置文件 ├── internal - │   ├── config - │   │   └── config.go // 配置定义 - │   ├── logic - │   │   └── checklogic.go // check业务逻辑在这里实现 - │   ├── server - │   │   └── checkerserver.go // 调用入口, 不需要修改 - │   └── svc - │   └── servicecontext.go // 定义ServiceContext,传递依赖 + │ ├── config + │ │ └── config.go // 配置定义 + │ ├── logic + │ │ └── checklogic.go // check业务逻辑在这里实现 + │ ├── server + │ │ └── checkerserver.go // 调用入口, 不需要修改 + │ └── svc + │ └── servicecontext.go // 定义ServiceContext,传递依赖 └── pb └── check.pb.go ``` @@ -386,11 +324,16 @@ Starting rpc server at 127.0.0.1:8081... ``` -## 9. 修改API Gateway代码调用check rpc服务 +## 8. 修改API Gateway代码调用add/check rpc服务 * 修改配置文件`bookstore-api.yaml`,增加如下内容 ```yaml + Add: + Etcd: + Hosts: + - localhost:2379 + Key: add.rpc Check: Etcd: Hosts: @@ -398,9 +341,9 @@ Key: check.rpc ``` - 通过etcd自动去发现可用的check服务 + 通过etcd自动去发现可用的add/check服务 -* 修改`internal/config/config.go`如下,增加add服务依赖 +* 修改`internal/config/config.go`如下,增加add/check服务依赖 ```go type Config struct { @@ -430,6 +373,28 @@ 通过ServiceContext在不同业务逻辑之间传递依赖 +* 修改`internal/logic/addlogic.go`里的`Add`方法,如下: + + ```go + func (l *AddLogic) Add(req types.AddReq) (*types.AddResp, error) { + // 手动代码开始 + resp, err := l.svcCtx.Adder.Add(l.ctx, &adder.AddReq{ + Book: req.Book, + Price: req.Price, + }) + if err != nil { + return nil, err + } + + return &types.AddResp{ + Ok: resp.Ok, + }, nil + // 手动代码结束 + } + ``` + + 通过调用`adder`的`Add`方法实现添加图书到bookstore系统 + * 修改`internal/logic/checklogic.go`里的`Check`方法,如下: ```go @@ -450,13 +415,13 @@ } ``` - 通过调用`checker`的`Check`方法实现从bookstore系统中查询图书的价格。 + 通过调用`checker`的`Check`方法实现从bookstore系统中查询图书的价格 -## 10. 定义数据库表结构,并生成CRUD+cache代码 +## 9. 定义数据库表结构,并生成CRUD+cache代码 * bookstore下创建`rpc/model`目录:`mkdir -p rpc/model` -* 在rpc/model目录下编写创建bookstore表的sql文件`bookstore.sql`,如下: +* 在rpc/model目录下编写创建book表的sql文件`book.sql`,如下: ```sql CREATE TABLE `book` @@ -474,13 +439,13 @@ ``` ```sql - source bookstore.sql; + source book.sql; ``` * 在`rpc/model`目录下执行如下命令生成CRUD+cache代码,`-c`表示使用`redis cache` ```shell - goctl model mysql ddl -c -src bookstore.sql -dir . + goctl model mysql ddl -c -src book.sql -dir . ``` 也可以用`datasource`命令代替`ddl`来指定数据库链接直接从schema生成 @@ -494,7 +459,7 @@ └── vars.go // 定义常量和变量 ``` -## 11. 修改add/check rpc代码调用crud+cache代码 +## 10. 修改add/check rpc代码调用crud+cache代码 * 修改`rpc/add/etc/add.yaml`和`rpc/check/etc/check.yaml`,增加如下内容: @@ -576,7 +541,7 @@ 至此代码修改完成,凡事手动修改的代码我加了标注 -## 12. 完整调用演示 +## 11. 完整调用演示 * add api调用 @@ -612,7 +577,7 @@ {"found":true,"price":10} ``` -## 13. Benchmark +## 12. Benchmark 因为写入依赖于mysql的写入速度,就相当于压mysql了,所以压测只测试了check接口,相当于从mysql里读取并利用缓存,为了方便,直接压这一本书,因为有缓存,多本书也是一样的,对压测结果没有影响。 @@ -633,7 +598,7 @@ Log: 可以看出在我的MacBook Pro上能达到3万+的qps。 -## 14. 总结 +## 13. 总结 我们一直强调**工具大于约定和文档**。 @@ -645,7 +610,3 @@ go-zero不只是一个框架,更是一个建立在框架+工具基础上的, 有任何好的提升工程效率的想法,随时欢迎交流!👏 -## 15. 项目地址 - -[https://github.com/tal-tech/go-zero](https://github.com/tal-tech/go-zero) - diff --git a/doc/images/bookstore-api.png b/doc/images/bookstore-api.png new file mode 100644 index 0000000000000000000000000000000000000000..d0f4f98f797a16e87f965f412aecf80fd19e5b2c GIT binary patch literal 106410 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