Forráskód Böngészése

update shorturl doc

kevin 4 éve
szülő
commit
6c4a4be5d2

BIN
doc/images/shorturl-benchmark.png


+ 349 - 32
doc/shorturl.md

@@ -12,10 +12,14 @@
 
 ## 2. 准备工作
 
-* 准备goctl工具,在任意目录下进行,目的是为了编译goctl工具
-  1. `git clone https://github.com/tal-tech/go-zero`
-  2. 在`tools/goctl`目录下编译goctl工具`go build goctl.go`
-  3. 将生成的goctl放到`$PATH`下,确保goctl命令可运行
+* 安装etcd, mysql, redis
+* 准备goctl工具
+* 直接从`https://github.com/tal-tech/go-zero/releases`下载最新版,后续会加上自动更新
+  * 也可以从源码编译,在任意目录下进行,目的是为了编译goctl工具
+  
+	1. `git clone https://github.com/tal-tech/go-zero`
+  	2. 在`tools/goctl`目录下编译goctl工具`go build goctl.go`
+  	3. 将生成的goctl放到`$PATH`下,确保goctl命令可运行
 * 创建工作目录`shorturl`
 * 在`shorturl`目录下执行`go mod init shorturl`初始化`go.mod`
 
@@ -128,6 +132,8 @@
 
 * 可以通过`goctl`生成各种客户端语言的api调用代码
 
+* 到这里,你已经可以通过goctl生成客户端代码给客户端同学并行开发了,支持多种语言,详见文档
+
 ## 4. 编写shorten rpc服务
 
 * 在`rpc/shorten`目录下编写`shorten.proto`文件
@@ -169,24 +175,24 @@
   ```
   rpc/shorten
   ├── etc
-│   └── shorten.yaml               // 配置文件
+  │   └── shorten.yaml               // 配置文件
   ├── internal
-│   ├── config
+  │   ├── config
   │   │   └── config.go              // 配置定义
-  │   ├── handler
-  │   │   └── shortenerhandler.go    // api handler, 不需要修改
   │   ├── logic
-│   │   └── shortenlogic.go        // api业务逻辑在这里实现
+  │   │   └── shortenlogic.go        // rpc业务逻辑在这里实现
+  │   ├── server
+  │   │   └── shortenerserver.go     // 调用入口, 不需要修改
   │   └── svc
   │       └── servicecontext.go      // 定义ServiceContext,传递依赖
   ├── pb
   │   └── shorten.pb.go
-  ├── shared
-  │   ├── shortenermodel.go          // 提供了外部调用方法,无需修改
-  │   ├── shortenermodel_mock.go     // mock方法,测试用
-  │   └── types.go                   // request/response结构体定义
   ├── shorten.go                     // rpc服务main函数
-  └── shorten.proto
+  ├── shorten.proto
+  └── shortener
+      ├── shortener.go               // 提供了外部调用方法,无需修改
+      ├── shortener_mock.go          // mock方法,测试用
+      └── types.go                   // request/response结构体定义
   ```
   
   直接可以运行,如下:
@@ -239,24 +245,24 @@
   ```
   rpc/expand
   ├── etc
-│   └── expand.yaml                // 配置文件
+  │   └── expand.yaml                // 配置文件
+  ├── expand.go                      // rpc服务main函数
+  ├── expand.proto
+  ├── expander
+  │   ├── expander.go                // 提供了外部调用方法,无需修改
+  │   ├── expander_mock.go           // mock方法,测试用
+  │   └── types.go                   // request/response结构体定义
   ├── internal
-│   ├── config
+  │   ├── config
   │   │   └── config.go              // 配置定义
-  │   ├── handler
-  │   │   └── expanderhandler.go     // api handler, 不需要修改
   │   ├── logic
-│   │   └── expandlogic.go         // api业务逻辑在这里实现
+  │   │   └── expandlogic.go         // rpc业务逻辑在这里实现
+  │   ├── server
+  │   │   └── expanderserver.go      // 调用入口, 不需要修改
   │   └── svc
   │       └── servicecontext.go      // 定义ServiceContext,传递依赖
-  ├── pb
-  │   └── expand.pb.go
-  ├── shared
-  │   ├── expandermodel.go           // 提供了外部调用方法,无需修改
-  │   ├── expandermodel_mock.go      // mock方法,测试用
-  │   └── types.go                   // request/response结构体定义
-  ├── expand.go                      // rpc服务main函数
-  └── expand.proto
+  └── pb
+      └── expand.pb.go
   ```
   
   修改`etc/expand.yaml`里面的`ListenOn`的端口为`8081`,因为`8080`已经被`shorten`服务占用了
@@ -270,7 +276,126 @@
   
   `etc/expand.yaml`文件里可以修改侦听端口等配置
 
-## 6. 修改API Gateway代码调用shorten/expand rpc服务(未完)
+## 6. 修改API Gateway代码调用shorten/expand rpc服务
+
+* 修改配置文件`shorter-api.yaml`,增加如下内容
+
+  ```yaml
+  Shortener:
+    Etcd:
+      Hosts:
+        - localhost:2379
+      Key: shorten.rpc
+  Expander:
+    Etcd:
+      Hosts:
+        - localhost:2379
+      Key: expand.rpc
+  ```
+
+  通过etcd自动去发现可用的shorten/expand服务
+
+* 修改`internal/config/config.go`如下,增加shorten/expand服务依赖
+
+  ```go
+  type Config struct {
+  	rest.RestConf
+  	Shortener rpcx.RpcClientConf     // 手动代码
+  	Expander  rpcx.RpcClientConf     // 手动代码
+  }
+  ```
+
+* 修改`internal/logic/expandlogic.go`,如下:
+
+  ```go
+  type ExpandLogic struct {
+  	ctx context.Context
+  	logx.Logger
+  	expander rpcx.Client            // 手动代码
+  }
+  
+  func NewExpandLogic(ctx context.Context, svcCtx *svc.ServiceContext) ExpandLogic {
+  	return ExpandLogic{
+  		ctx:    ctx,
+  		Logger: logx.WithContext(ctx),
+  		expander: svcCtx.Expander,    // 手动代码
+  	}
+  }
+  
+  func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) {
+    // 手动代码开始
+  	resp, err := expander.NewExpander(l.expander).Expand(l.ctx, &expander.ExpandReq{
+  		Key: req.Key,
+  	})
+  	if err != nil {
+  		return nil, err
+  	}
+  
+  	return &types.ExpandResp{
+  		Url: resp.Url,
+  	}, nil
+    // 手动代码结束
+  }
+  ```
+
+  增加了对`expander`服务的依赖,并通过调用`expander`的`Expand`方法实现短链恢复到url
+
+* 修改`internal/logic/shortenlogic.go`,如下:
+
+  ```go
+  type ShortenLogic struct {
+  	ctx context.Context
+  	logx.Logger
+  	shortener rpcx.Client             // 手动代码
+  }
+  
+  func NewShortenLogic(ctx context.Context, svcCtx *svc.ServiceContext) ShortenLogic {
+  	return ShortenLogic{
+  		ctx:    ctx,
+  		Logger: logx.WithContext(ctx),
+  		shortener: svcCtx.Shortener,    // 手动代码
+  	}
+  }
+  
+  func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) {
+    // 手动代码开始
+  	resp, err := shortener.NewShortener(l.shortener).Shorten(l.ctx, &shortener.ShortenReq{
+  		Url: req.Url,
+  	})
+  	if err != nil {
+  		return nil, err
+  	}
+  
+  	return &types.ShortenResp{
+  		ShortUrl: resp.Key,
+  	}, nil
+    // 手动代码结束
+  }
+  ```
+
+   增加了对`shortener`服务的依赖,并通过调用`shortener`的`Shorten`方法实现url到短链的变换
+
+* 修改`internal/svc/servicecontext.go`,如下:
+
+  ```go
+  type ServiceContext struct {
+  	Config    config.Config
+  	Shortener rpcx.Client                                 // 手动代码
+  	Expander  rpcx.Client                                 // 手动代码
+  }
+  
+  func NewServiceContext(config config.Config) *ServiceContext {
+  	return &ServiceContext{
+  		Config:    config,
+  		Shortener: rpcx.MustNewClient(config.Shortener),    // 手动代码
+  		Expander:  rpcx.MustNewClient(config.Expander),     // 手动代码
+  	}
+  }
+  ```
+
+  通过ServiceContext在不同业务逻辑之间传递依赖
+
+  至此,API Gateway修改完成,虽然贴的代码多,但是期中修改的是很少的一部分,为了方便理解上下文,我贴了完整代码,接下来处理CRUD+cache
 
 ## 7. 定义数据库表结构,并生成CRUD+cache代码
 
@@ -317,14 +442,206 @@
 
 ## 8. 修改shorten/expand rpc代码调用crud+cache代码
 
+* 修改`rpc/expand/etc/expand.yaml`,增加如下内容:
+
+  ```yaml
+  DataSource: root:@tcp(localhost:3306)/gozero
+  Table: shorturl
+  Cache:
+    - Host: localhost:6379
+  ```
+
+  可以使用多个redis作为cache,支持redis单点或者redis集群
+
+* 修改`rpc/expand/internal/config.go`,如下:
+
+  ```go
+  type Config struct {
+  	rpcx.RpcServerConf
+  	DataSource string             // 手动代码
+  	Table      string             // 手动代码
+  	Cache      cache.CacheConf    // 手动代码
+  }
+  ```
+
+  增加了mysql和redis cache配置
+
+* 修改`rpc/expand/internal/svc/servicecontext.go`,如下:
+
+  ```go
+  type ServiceContext struct {
+  	c     config.Config
+  	Model *model.ShorturlModel   // 手动代码
+  }
+  
+  func NewServiceContext(c config.Config) *ServiceContext {
+  	return &ServiceContext{
+  		c:     c,
+  		Model: model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // 手动代码
+  	}
+  }
+  ```
+
+* 修改`rpc/expand/internal/logic/expandlogic.go`,如下:
+
+  ```go
+  type ExpandLogic struct {
+  	ctx context.Context
+  	logx.Logger
+  	model *model.ShorturlModel          // 手动代码
+  }
+  
+  func NewExpandLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ExpandLogic {
+  	return &ExpandLogic{
+  		ctx:    ctx,
+  		Logger: logx.WithContext(ctx),
+  		model:  svcCtx.Model,             // 手动代码
+  	}
+  }
+  
+  func (l *ExpandLogic) Expand(in *expand.ExpandReq) (*expand.ExpandResp, error) {
+    // 手动代码开始
+  	res, err := l.model.FindOne(in.Key)
+  	if err != nil {
+  		return nil, err
+  	}
+  
+  	return &expand.ExpandResp{
+  		Url: res.Url,
+  	}, nil
+    // 手动代码结束
+  }
+  ```
+
+* 修改`rpc/shorten/etc/shorten.yaml`,增加如下内容:
+
+  ```yaml
+  DataSource: root:@tcp(localhost:3306)/gozero
+  Table: shorturl
+  Cache:
+    - Host: localhost:6379
+  ```
+
+  可以使用多个redis作为cache,支持redis单点或者redis集群
+
+* 修改`rpc/shorten/internal/config.go`,如下:
+
+  ```go
+  type Config struct {
+  	rpcx.RpcServerConf
+  	DataSource string            // 手动代码
+  	Table      string            // 手动代码
+  	Cache      cache.CacheConf   // 手动代码
+  }
+  ```
+
+  增加了mysql和redis cache配置
+
+* 修改`rpc/shorten/internal/svc/servicecontext.go`,如下:
+
+  ```go
+  type ServiceContext struct {
+  	c     config.Config
+  	Model *model.ShorturlModel   // 手动代码
+  }
+  
+  func NewServiceContext(c config.Config) *ServiceContext {
+  	return &ServiceContext{
+  		c:     c,
+  		Model: model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // 手动代码
+  	}
+  }
+  ```
+
+* 修改`rpc/shorten/internal/logic/shortenlogic.go`,如下:
+
+  ```go
+  const keyLen = 6
+  
+  type ShortenLogic struct {
+  	ctx context.Context
+  	logx.Logger
+  	model *model.ShorturlModel          // 手动代码
+  }
+  
+  func NewShortenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ShortenLogic {
+  	return &ShortenLogic{
+  		ctx:    ctx,
+  		Logger: logx.WithContext(ctx),
+  		model:  svcCtx.Model,             // 手动代码
+  	}
+  }
+  
+  func (l *ShortenLogic) Shorten(in *shorten.ShortenReq) (*shorten.ShortenResp, error) {
+    // 手动代码开始,生成短链接
+  	key := hash.Md5Hex([]byte(in.Url))[:keyLen]
+  	_, err := l.model.Insert(model.Shorturl{
+  		Shorten: key,
+  		Url:     in.Url,
+  	})
+  	if err != nil {
+  		return nil, err
+  	}
+  
+  	return &shorten.ShortenResp{
+  		Key: key,
+  	}, nil
+    // 手动代码结束
+  }
+  ```
+
+  至此代码修改完成,凡事手动修改的代码我加了标注
+
 ## 9. 完整调用演示
 
-## 10. Benchmark(未完)
+* shorten api调用
+
+  ```shell
+  ~ curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn"
+  ```
+
+  返回如下:
 
-## 11. 总结(未完)
+  ```http
+  HTTP/1.1 200 OK
+  Content-Type: application/json
+  Date: Sat, 29 Aug 2020 10:49:49 GMT
+  Content-Length: 21
+  
+  {"shortUrl":"f35b2a"}
+  ```
 
-可以看到go-zero不只是一个框架,更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。
+* expand api调用
+
+  ```shell
+  curl -i "http://localhost:8888/expand?key=f35b2a"
+  ```
+
+  返回如下:
+
+  ```http
+  HTTP/1.1 200 OK
+  Content-Type: application/json
+  Date: Sat, 29 Aug 2020 10:51:53 GMT
+  Content-Length: 34
+  
+  {"url":"http://www.xiaoheiban.cn"}
+  ```
+
+## 10. Benchmark
+
+因为写入依赖于mysql的写入速度,就相当于压mysql了,所以压测只测试了expand接口,相当于从mysql里读取并利用缓存,shorten.lua里随机从db里获取了100个热key来生成压测请求
+
+![Benchmark](images/shorturl-benchmark.png)
+
+可以看出在我的MacBook Pro上能达到3万+的qps。
+
+## 11. 总结
 
 我们一直强调**工具大于约定和文档**。
 
-另外,我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。
+go-zero不只是一个框架,更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。
+
+我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。
+
+通过go-zero+goctl生成的代码,包含了微服务治理的各种组件,包括:并发控制、自适应熔断、自适应降载、自动缓存控制等,可以轻松部署以承载巨大访问量。

+ 2 - 0
readme.md

@@ -89,6 +89,8 @@ go get -u github.com/tal-tech/go-zero
 
 ## 6. Quick Start
 
+0. 完整示例请查看[从0到1快速构建一个高并发的微服务系统](doc/shorturl.md)
+
 1. 编译goctl工具
 
    ```shell

+ 2 - 2
tools/goctl/api/gogen/genhandlers.go

@@ -24,7 +24,6 @@ import (
 
 func {{.handlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
-		l := logic.{{.logic}}(r.Context(), ctx)
 		{{.handlerBody}}
 	}
 }
@@ -39,6 +38,7 @@ func {{.handlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
 		}
 `
 	hasRespTemplate = `
+		l := logic.{{.logic}}(r.Context(), ctx)
 		{{.logicResponse}} l.{{.callee}}({{.req}})
 		if err != nil {
 			httpx.Error(w, err)
@@ -84,6 +84,7 @@ func genHandler(dir string, group spec.Group, route spec.Route) error {
 	var logicBodyBuilder strings.Builder
 	t := template.Must(template.New("hasRespTemplate").Parse(hasRespTemplate))
 	if err := t.Execute(&logicBodyBuilder, map[string]string{
+		"logic":         "New" + strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
 		"callee":        strings.Title(strings.TrimSuffix(handler, "Handler")),
 		"req":           req,
 		"logicResponse": logicResponse,
@@ -134,7 +135,6 @@ func doGenToFile(dir, handler string, group spec.Group, route spec.Route, bodyBu
 	t := template.Must(template.New("handlerTemplate").Parse(handlerTemplate))
 	buffer := new(bytes.Buffer)
 	err = t.Execute(buffer, map[string]string{
-		"logic":          "New" + strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
 		"importPackages": genHandlerImports(group, route, parentPkg),
 		"handlerName":    handler,
 		"handlerBody":    strings.TrimSpace(bodyBuilder.String()),

+ 2 - 3
tools/goctl/api/gogen/genservicecontext.go → tools/goctl/api/gogen/gensvc.go

@@ -20,10 +20,9 @@ type ServiceContext struct {
 	Config {{.config}}
 }
 
-func NewServiceContext(config {{.config}}) *ServiceContext {
-	return &ServiceContext{Config: config}
+func NewServiceContext(c {{.config}}) *ServiceContext {
+	return &ServiceContext{Config: c}
 }
-
 `
 )
 

+ 1 - 1
tools/goctl/model/sql/template/delete.go

@@ -2,7 +2,7 @@ package template
 
 var Delete = `
 func (m *{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error {
-	{{if .withCache}}{{if .containsIndexCache}}data,err:=m.FindOne({{.lowerStartCamelPrimaryKey}})
+	{{if .withCache}}{{if .containsIndexCache}}_, err:=m.FindOne({{.lowerStartCamelPrimaryKey}})
 	if err!=nil{
 		return err
 	}{{end}}

+ 0 - 1
tools/goctl/model/sql/template/import.go

@@ -5,7 +5,6 @@ var (
 	"database/sql"
 	"fmt"
 	"strings"
-	"time"
 
 	"github.com/tal-tech/go-zero/core/stores/cache"
 	"github.com/tal-tech/go-zero/core/stores/sqlc"

+ 1 - 1
tools/goctl/model/sql/template/insert.go

@@ -2,7 +2,7 @@ package template
 
 var Insert = `
 func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result, error) {
-	query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "`(` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) value ({{.expression}})` " + `
+	query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
 	return m.{{if .withCache}}ExecNoCache{{else}}conn.Exec{{end}}(query, {{.expressionValues}})
 }
 `

+ 13 - 25
tools/goctl/rpc/ctx/ctx.go

@@ -16,27 +16,23 @@ import (
 const (
 	flagSrc     = "src"
 	flagDir     = "dir"
-	flagShared  = "shared"
 	flagService = "service"
 	flagIdea    = "idea"
 )
 
-type (
-	RpcContext struct {
-		ProjectPath  string
-		ProjectName  stringx.String
-		ServiceName  stringx.String
-		CurrentPath  string
-		Module       string
-		ProtoFileSrc string
-		ProtoSource  string
-		TargetDir    string
-		SharedDir    string
-		console.Console
-	}
-)
+type RpcContext struct {
+	ProjectPath  string
+	ProjectName  stringx.String
+	ServiceName  stringx.String
+	CurrentPath  string
+	Module       string
+	ProtoFileSrc string
+	ProtoSource  string
+	TargetDir    string
+	console.Console
+}
 
-func MustCreateRpcContext(protoSrc, targetDir, sharedDir, serviceName string, idea bool) *RpcContext {
+func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *RpcContext {
 	log := console.NewConsole(idea)
 	info, err := prepare(log)
 	log.Must(err)
@@ -54,15 +50,9 @@ func MustCreateRpcContext(protoSrc, targetDir, sharedDir, serviceName string, id
 	if stringx.From(targetDir).IsEmptyOrSpace() {
 		targetDir = current
 	}
-	if stringx.From(sharedDir).IsEmptyOrSpace() {
-		sharedDir = filepath.Join(current, "shared")
-	}
 	targetDirFp, err := filepath.Abs(targetDir)
 	log.Must(err)
 
-	sharedFp, err := filepath.Abs(sharedDir)
-	log.Must(err)
-
 	if stringx.From(serviceName).IsEmptyOrSpace() {
 		serviceName = getServiceFromRpcStructure(targetDirFp)
 	}
@@ -80,7 +70,6 @@ func MustCreateRpcContext(protoSrc, targetDir, sharedDir, serviceName string, id
 		ProtoFileSrc: srcFp,
 		ProtoSource:  filepath.Base(srcFp),
 		TargetDir:    targetDirFp,
-		SharedDir:    sharedFp,
 		Console:      log,
 	}
 }
@@ -93,10 +82,9 @@ func MustCreateRpcContextFromCli(ctx *cli.Context) *RpcContext {
 	}
 	protoSrc := ctx.String(flagSrc)
 	targetDir := ctx.String(flagDir)
-	sharedDir := ctx.String(flagShared)
 	serviceName := ctx.String(flagService)
 	idea := ctx.Bool(flagIdea)
-	return MustCreateRpcContext(protoSrc, targetDir, sharedDir, serviceName, idea)
+	return MustCreateRpcContext(protoSrc, targetDir, serviceName, idea)
 }
 
 func getServiceFromRpcStructure(targetDir string) string {

+ 7 - 10
tools/goctl/rpc/gen/gen.go

@@ -10,8 +10,7 @@ const (
 	dirConfig          = "config"
 	dirEtc             = "etc"
 	dirSvc             = "svc"
-	dirShared          = "shared"
-	dirHandler         = "handler"
+	dirServer          = "server"
 	dirLogic           = "logic"
 	dirPb              = "pb"
 	dirInternal        = "internal"
@@ -19,13 +18,11 @@ const (
 	fileServiceContext = "servicecontext.go"
 )
 
-type (
-	defaultRpcGenerator struct {
-		dirM map[string]string
-		Ctx  *ctx.RpcContext
-		ast  *parser.PbAst
-	}
-)
+type defaultRpcGenerator struct {
+	dirM map[string]string
+	Ctx  *ctx.RpcContext
+	ast  *parser.PbAst
+}
 
 func NewDefaultRpcGenerator(ctx *ctx.RpcContext) *defaultRpcGenerator {
 	return &defaultRpcGenerator{
@@ -80,7 +77,7 @@ func (g *defaultRpcGenerator) Generate() (err error) {
 		return
 	}
 
-	err = g.genShared()
+	err = g.genCall()
 	if err != nil {
 		return
 	}

+ 76 - 54
tools/goctl/rpc/gen/genshared.go → tools/goctl/rpc/gen/gencall.go

@@ -13,9 +13,9 @@ import (
 )
 
 const (
-	sharedTemplateText = `{{.head}}
+	callTemplateText = `{{.head}}
 
-//go:generate mockgen -destination ./{{.name}}model_mock.go -package {{.filePackage}} -source $GOFILE
+//go:generate mockgen -destination ./{{.name}}_mock.go -package {{.filePackage}} -source $GOFILE
 
 package {{.filePackage}}
 
@@ -29,24 +29,24 @@ import (
 )
 
 type (
-	{{.serviceName}}Model interface {
+	{{.serviceName}} interface {
 		{{.interface}}
 	}
 
-	default{{.serviceName}}Model struct {
+	default{{.serviceName}} struct {
 		cli rpcx.Client
 	}
 )
 
-func New{{.serviceName}}Model(cli rpcx.Client) {{.serviceName}}Model {
-	return &default{{.serviceName}}Model{
+func New{{.serviceName}}(cli rpcx.Client) {{.serviceName}} {
+	return &default{{.serviceName}}{
 		cli: cli,
 	}
 }
 
 {{.functions}}
 `
-	sharedTemplateTypes = `{{.head}}
+	callTemplateTypes = `{{.head}}
 
 package {{.filePackage}}
 
@@ -56,11 +56,11 @@ var errJsonConvert = errors.New("json convert error")
 
 {{.types}}
 `
-	sharedInterfaceFunctionTemplate = `{{if .hasComment}}{{.comment}}
+	callInterfaceFunctionTemplate = `{{if .hasComment}}{{.comment}}
 {{end}}{{.method}}(ctx context.Context,in *{{.pbRequest}}) {{if .hasResponse}}(*{{.pbResponse}},{{end}} error{{if .hasResponse}}){{end}}`
-	sharedFunctionTemplate = `
+	callFunctionTemplate = `
 {{if .hasComment}}{{.comment}}{{end}}
-func (m *default{{.rpcServiceName}}Model) {{.method}}(ctx context.Context,in *{{.pbRequest}}) {{if .hasResponse}}(*{{.pbResponse}},{{end}} error{{if .hasResponse}}){{end}} {
+func (m *default{{.rpcServiceName}}) {{.method}}(ctx context.Context,in *{{.pbRequest}}) {{if .hasResponse}}(*{{.pbResponse}},{{end}} error{{if .hasResponse}}){{end}} {
 	var request {{.package}}.{{.pbRequest}}
 	bts, err := jsonx.Marshal(in)
 	if err != nil {
@@ -98,59 +98,78 @@ func (m *default{{.rpcServiceName}}Model) {{.method}}(ctx context.Context,in *{{
 `
 )
 
-func (g *defaultRpcGenerator) genShared() error {
-	sharePackage := filepath.Base(g.Ctx.SharedDir)
+func (g *defaultRpcGenerator) genCall() error {
 	file := g.ast
+	if len(file.Service) == 0 {
+		return nil
+	}
+	if len(file.Service) > 1 {
+		return fmt.Errorf("we recommend only one service in a proto, currently %d", len(file.Service))
+	}
+
 	typeCode, err := file.GenTypesCode()
 	if err != nil {
 		return err
 	}
 
+	service := file.Service[0]
+	callPath, err := filepath.Abs(service.Name.Lower())
+	if err != nil {
+		return err
+	}
+
+	if err = util.MkdirIfNotExist(callPath); err != nil {
+		return err
+	}
+
 	pbPkg := file.Package
 	remotePackage := fmt.Sprintf(`%v "%v"`, pbPkg, g.mustGetPackage(dirPb))
-	filename := filepath.Join(g.Ctx.SharedDir, "types.go")
+	filename := filepath.Join(callPath, "types.go")
 	head := util.GetHead(g.Ctx.ProtoSource)
-	err = util.With("types").GoFmt(true).Parse(sharedTemplateTypes).SaveTo(map[string]interface{}{
+	err = util.With("types").GoFmt(true).Parse(callTemplateTypes).SaveTo(map[string]interface{}{
 		"head":                  head,
-		"filePackage":           sharePackage,
+		"filePackage":           service.Name.Lower(),
 		"pbPkg":                 pbPkg,
 		"serviceName":           g.Ctx.ServiceName.Title(),
 		"lowerStartServiceName": g.Ctx.ServiceName.UnTitle(),
 		"types":                 typeCode,
 	}, filename, true)
+	if err != nil {
+		return err
+	}
 
 	_, err = exec.LookPath("mockgen")
 	mockGenInstalled := err == nil
-	for _, service := range file.Service {
-		filename := filepath.Join(g.Ctx.SharedDir, fmt.Sprintf("%smodel.go", service.Name.Lower()))
-		functions, err := g.getFuncs(service)
-		if err != nil {
-			return err
-		}
-		iFunctions, err := g.getInterfaceFuncs(service)
-		if err != nil {
-			return err
-		}
-		mockFile := filepath.Join(g.Ctx.SharedDir, fmt.Sprintf("%smodel_mock.go", service.Name.Lower()))
-		os.Remove(mockFile)
-		err = util.With("shared").GoFmt(true).Parse(sharedTemplateText).SaveTo(map[string]interface{}{
-			"name":        service.Name.Lower(),
-			"head":        head,
-			"filePackage": sharePackage,
-			"pbPkg":       pbPkg,
-			"package":     remotePackage,
-			"serviceName": service.Name.Title(),
-			"functions":   strings.Join(functions, "\n"),
-			"interface":   strings.Join(iFunctions, "\n"),
-		}, filename, true)
-		if err != nil {
-			return err
-		}
-		// if mockgen is already installed, it will generate code of gomock for shared files
-		_, err = exec.LookPath("mockgen")
-		if mockGenInstalled {
-			execx.Run(fmt.Sprintf("go generate %s", filename))
-		}
+	filename = filepath.Join(callPath, fmt.Sprintf("%s.go", service.Name.Lower()))
+	functions, err := g.getFuncs(service)
+	if err != nil {
+		return err
+	}
+
+	iFunctions, err := g.getInterfaceFuncs(service)
+	if err != nil {
+		return err
+	}
+
+	mockFile := filepath.Join(callPath, fmt.Sprintf("%s_mock.go", service.Name.Lower()))
+	os.Remove(mockFile)
+	err = util.With("shared").GoFmt(true).Parse(callTemplateText).SaveTo(map[string]interface{}{
+		"name":        service.Name.Lower(),
+		"head":        head,
+		"filePackage": service.Name.Lower(),
+		"pbPkg":       pbPkg,
+		"package":     remotePackage,
+		"serviceName": service.Name.Title(),
+		"functions":   strings.Join(functions, "\n"),
+		"interface":   strings.Join(iFunctions, "\n"),
+	}, filename, true)
+	if err != nil {
+		return err
+	}
+	// if mockgen is already installed, it will generate code of gomock for shared files
+	_, err = exec.LookPath("mockgen")
+	if mockGenInstalled {
+		execx.Run(fmt.Sprintf("go generate %s", filename))
 	}
 
 	return nil
@@ -169,7 +188,7 @@ func (g *defaultRpcGenerator) getFuncs(service *parser.RpcService) ([]string, er
 		if len(method.Document) > 0 {
 			comment = method.Document[0]
 		}
-		buffer, err := util.With("sharedFn").Parse(sharedFunctionTemplate).Execute(map[string]interface{}{
+		buffer, err := util.With("sharedFn").Parse(callFunctionTemplate).Execute(map[string]interface{}{
 			"rpcServiceName": service.Name.Title(),
 			"method":         method.Name.Title(),
 			"package":        pkgName,
@@ -191,6 +210,7 @@ func (g *defaultRpcGenerator) getFuncs(service *parser.RpcService) ([]string, er
 func (g *defaultRpcGenerator) getInterfaceFuncs(service *parser.RpcService) ([]string, error) {
 	file := g.ast
 	functions := make([]string, 0)
+
 	for _, method := range service.Funcs {
 		data, found := file.Strcuts[strings.ToLower(method.OutType)]
 		if found {
@@ -200,19 +220,21 @@ func (g *defaultRpcGenerator) getInterfaceFuncs(service *parser.RpcService) ([]s
 		if len(method.Document) > 0 {
 			comment = method.Document[0]
 		}
-		buffer, err := util.With("interfaceFn").Parse(sharedInterfaceFunctionTemplate).Execute(map[string]interface{}{
-			"hasComment":  len(method.Document) > 0,
-			"comment":     comment,
-			"method":      method.Name.Title(),
-			"pbRequest":   method.InType,
-			"pbResponse":  method.OutType,
-			"hasResponse": found,
-		})
+		buffer, err := util.With("interfaceFn").Parse(callInterfaceFunctionTemplate).Execute(
+			map[string]interface{}{
+				"hasComment":  len(method.Document) > 0,
+				"comment":     comment,
+				"method":      method.Name.Title(),
+				"pbRequest":   method.InType,
+				"pbResponse":  method.OutType,
+				"hasResponse": found,
+			})
 		if err != nil {
 			return nil, err
 		}
 
 		functions = append(functions, buffer.String())
 	}
+
 	return functions, nil
 }

+ 1 - 2
tools/goctl/rpc/gen/gendir.go

@@ -23,11 +23,10 @@ func (g *defaultRpcGenerator) createDir() error {
 	m[dirEtc] = filepath.Join(ctx.TargetDir, dirEtc)
 	m[dirInternal] = filepath.Join(ctx.TargetDir, dirInternal)
 	m[dirConfig] = filepath.Join(ctx.TargetDir, dirInternal, dirConfig)
-	m[dirHandler] = filepath.Join(ctx.TargetDir, dirInternal, dirHandler)
+	m[dirServer] = filepath.Join(ctx.TargetDir, dirInternal, dirServer)
 	m[dirLogic] = filepath.Join(ctx.TargetDir, dirInternal, dirLogic)
 	m[dirPb] = filepath.Join(ctx.TargetDir, dirPb)
 	m[dirSvc] = filepath.Join(ctx.TargetDir, dirInternal, dirSvc)
-	m[dirShared] = g.Ctx.SharedDir
 	for _, d := range m {
 		err := util.MkdirIfNotExist(d)
 		if err != nil {

+ 1 - 1
tools/goctl/rpc/gen/genetc.go

@@ -13,7 +13,7 @@ Log:
 ListenOn: 127.0.0.1:8080
 Etcd:
   Hosts:
-  - 127.0.0.1:6379
+  - 127.0.0.1:2379
   Key: {{.serviceName}}.rpc
 `
 

+ 1 - 3
tools/goctl/rpc/gen/genlogic.go

@@ -36,11 +36,9 @@ func New{{.logicName}}(ctx context.Context,svcCtx *svc.ServiceContext) *{{.logic
 `
 	logicFunctionTemplate = `{{if .hasComment}}{{.comment}}{{end}}
 func (l *{{.logicName}}) {{.method}} (in *{{.package}}.{{.request}}) (*{{.package}}.{{.response}}, error) {
-	var resp {{.package}}.{{.response}}
-
 	// todo: add your logic here and delete this line
 	
-	return &resp,nil
+	return &{{.package}}.{{.response}}{}, nil
 }
 `
 )

+ 2 - 2
tools/goctl/rpc/gen/genmain.go

@@ -56,7 +56,7 @@ func (g *defaultRpcGenerator) genMain() error {
 	imports := make([]string, 0)
 	pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb))
 	svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc))
-	remoteImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirHandler))
+	remoteImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirServer))
 	configImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirConfig))
 	imports = append(imports, configImport, pbImport, remoteImport, svcImport)
 	srv, registers := g.genServer(pkg, file.Service)
@@ -76,7 +76,7 @@ func (g *defaultRpcGenerator) genServer(pkg string, list []*parser.RpcService) (
 	list2 := make([]string, 0)
 	for _, item := range list {
 		name := item.Name.UnTitle()
-		list1 = append(list1, fmt.Sprintf("%sSrv := handler.New%sServer(ctx)", name, item.Name.Title()))
+		list1 = append(list1, fmt.Sprintf("%sSrv := server.New%sServer(ctx)", name, item.Name.Title()))
 		list2 = append(list2, fmt.Sprintf("%s.Register%sServer(grpcServer, %sSrv)", pkg, item.Name.Title(), name))
 	}
 	return strings.Join(list1, "\n"), strings.Join(list2, "\n")

+ 7 - 7
tools/goctl/rpc/gen/genhandler.go → tools/goctl/rpc/gen/genserver.go

@@ -10,9 +10,9 @@ import (
 )
 
 const (
-	handlerTemplate = `{{.head}}
+	serverTemplate = `{{.head}}
 
-package handler
+package server
 
 import (
 	"context"
@@ -43,7 +43,7 @@ func (s *{{.server}}Server) {{.method}} (ctx context.Context, in *{{.package}}.{
 )
 
 func (g *defaultRpcGenerator) genHandler() error {
-	handlerPath := g.dirM[dirHandler]
+	serverPath := g.dirM[dirServer]
 	file := g.ast
 	pkg := file.Package
 	pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb))
@@ -56,19 +56,19 @@ func (g *defaultRpcGenerator) genHandler() error {
 	}
 	head := util.GetHead(g.Ctx.ProtoSource)
 	for _, service := range file.Service {
-		filename := fmt.Sprintf("%vhandler.go", service.Name.Lower())
-		handlerFile := filepath.Join(handlerPath, filename)
+		filename := fmt.Sprintf("%vserver.go", service.Name.Lower())
+		serverFile := filepath.Join(serverPath, filename)
 		funcList, err := g.genFunctions(service)
 		if err != nil {
 			return err
 		}
-		err = util.With("server").GoFmt(true).Parse(handlerTemplate).SaveTo(map[string]interface{}{
+		err = util.With("server").GoFmt(true).Parse(serverTemplate).SaveTo(map[string]interface{}{
 			"head":    head,
 			"types":   fmt.Sprintf(typeFmt, service.Name.Title()),
 			"server":  service.Name.Title(),
 			"imports": strings.Join(imports, "\n\t"),
 			"funcs":   strings.Join(funcList, "\n"),
-		}, handlerFile, true)
+		}, serverFile, true)
 		if err != nil {
 			return err
 		}