目录
- api
- cmd
- configs
- dao
- di
- model
- server
- service
上篇文章go微服务框架kratos学习笔记一(kratos demo)跑了kratos demo
本章来看看demo项目的整体结构。
目录结构
├─api # 对外接口
├─cmd # main
├─configs # 配置
├─internal
│ ├─dao #数据访问
│ ├─di #依赖注入
│ ├─model #业务结构体的声明
│ ├─server #grpc、http初始化
│ │ ├─grpc
│ │ └─http
│ └─service #业务逻辑处理
└─test
官方文档解释
├── CHANGELOG.md
├── OWNERS
├── README.md
├── api # api目录为对外保留的proto文件及生成的pb.go文件
│ ├── api.bm.go
│ ├── api.pb.go # 通过go generate生成的pb.go文件
│ ├── api.proto
│ └── client.go
├── cmd
│ └── main.go # cmd目录为main所在
├── configs # configs为配置文件目录
│ ├── application.toml # 应用的自定义配置文件,可能是一些业务开关如:useABtest = true
│ ├── db.toml # db相关配置
│ ├── grpc.toml # grpc相关配置
│ ├── http.toml # http相关配置
│ ├── memcache.toml # memcache相关配置
│ └── redis.toml # redis相关配置
├── go.mod
├── go.sum
└── internal # internal为项目内部包,包括以下目录:
│ ├── dao # dao层,用于数据库、cache、MQ、依赖某业务grpc|http等资源访问
│ │ ├── dao.bts.go
│ │ ├── dao.go
│ │ ├── db.go
│ │ ├── mc.cache.go
│ │ ├── mc.go
│ │ └── redis.go
│ ├── di # 依赖注入层 采用wire静态分析依赖
│ │ ├── app.go
│ │ ├── wire.go # wire 声明
│ │ └── wire_gen.go # go generate 生成的代码
│ ├── model # model层,用于声明业务结构体
│ │ └── model.go
│ ├── server # server层,用于初始化grpc和http server
│ │ ├── grpc # grpc层,用于初始化grpc server和定义method
│ │ │ └── server.go
│ │ └── http # http层,用于初始化http server和声明handler
│ │ └── server.go
│ └── service # service层,用于业务逻辑处理,且为方便http和grpc共用方法,建议入参和出参保持grpc风格,且使用pb文件生成代码
│ └── service.go
└── test # 测试资源层 用于存放测试相关资源数据 如docker-compose配置 数据库初始化语句等
└── docker-compose.yaml
下面简单看看各层目录,api应该是最复杂的部分,其他的都很好看懂。
api
├── api # api目录为对外保留的proto文件及生成的pb.go文件
│ ├── api.bm.go
│ ├── api.pb.go # 通过go generate生成的pb.go文件
│ ├── api.proto
│ └── client.go
api目录主要为对外接口目录、
api.bm.go
、apb.pb.go
可以通过kartos tool生成(kratos tool可以基于proto生成http&grpc代码,生成缓存回源代码,生成memcache执行代码,生成swagger文档等工具集) bm、 pb 分别为http和grpc的接口。
C:\server\src\go\src\demp\api>kratos tool protoc --grpc --bm api.proto
go get -u github.com/bilibili/kratos/tool/kratos-protoc
protoc: 安装成功!
2019/12/23 17:48:51 protoc --proto_path=C:\server\src\go/src --proto_path=C:\server\src\go/pkg/mod/github.com/bilibili/[email protected]/third_party --proto_path=C:\server\src\go\src\demp\api --bm_out=:. api.proto
2019/12/23 17:48:52 protoc --proto_path=C:\server\src\go/src --proto_path=C:\server\src\go/pkg/mod/github.com/bilibili/[email protected]/third_party --proto_path=C:\server\src\go\src\demp\api --gofast_out=plugins=grpc:. api.proto
2019/12/23 17:48:53 generate api.proto success.
api.bm.go
为http的对外接口, BM server即blademaster为热度http框架gin的裁剪.去除了gin中不需要的部分逻辑,
api目录主要为对外目录、
api.bm.go
、apb.pb.go
可以通过kartos tool生成(kratos tool可以基于proto生成http&grpc代码,生成缓存回源代码,生成memcache执行代码,生成swagger文档等工具集) bm、 pb 分别为http和grpc的接口。
像上篇文章,protoc 没装 不能运行的情况下,其实可以用kratos tool 来生成 对应go文件的。
C:\server\src\go\src\demp\api>kratos tool protoc --grpc --bm api.proto
I:.
api.proto
client.go
没有子文件夹
I:\VSProject\go\src\demo\api>kratos tool protoc --grpc --bm api.proto
go get -u github.com/bilibili/kratos/tool/kratos-protoc
protoc: 安装成功!
2019/12/24 21:13:18 go get -u github.com/bilibili/kratos/tool/protobuf/protoc-gen-bm
go: downloading github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726
go: extracting github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726
go: finding google.golang.org/genproto latest
go: finding github.com/siddontang/go latest
go: downloading google.golang.org/genproto v0.0.0-20191223191004-3caeed10a8bf
go: extracting google.golang.org/genproto v0.0.0-20191223191004-3caeed10a8bf
2019/12/24 21:13:37 protoc --proto_path=I:\VSProject\go/src --proto_path=I:\VSProject\go/pkg/mod/github.com/bilibili/[email protected]/third_party --proto_path=I:\VSProject\go\src\demo\api --bm_out=:. api.proto
2019/12/24 21:13:37 go get -u github.com/gogo/protobuf/protoc-gen-gofast
go: finding github.com/gogo/protobuf v1.3.1
go: downloading github.com/gogo/protobuf v1.3.1
go: extracting github.com/gogo/protobuf v1.3.1
2019/12/24 21:13:46 protoc --proto_path=I:\VSProject\go/src --proto_path=I:\VSProject\go/pkg/mod/github.com/bilibili/[email protected]/third_party --proto_path=I:\VSProject\go\src\demo\api --gofast_out=plugins=grpc:. api.proto
2019/12/24 21:13:47 generate api.proto success.
I:\VSProject\go\src\demo\api>tree /f
I:.
api.bm.go
api.pb.go
api.proto
client.go
但这样还是不够运行,错误是缺少di.InitApp(), 对比上次笔记(一)的正常项目,会发现还少了一个wire_gen.go文件
I:\VSProject\go\src\demo\internal\di>kratos run
# command-line-arguments
.\main.go:21:23: undefined: di.InitApp
panic: exit status 2
goroutine 1 [running]:
main.runAction(0xc000102160, 0x0, 0xc0000ee170)
I:/VSProject/go/pkg/mod/github.com/bilibili/[email protected]/tool/kratos/run.go:25 +0x36e
github.com/urfave/cli.HandleAction(0x603080, 0x65fdc8, 0xc000102160, 0xc000102160, 0x0)
I:/VSProject/go/pkg/mod/github.com/urfave/[email protected]/app.go:523 +0xc5
github.com/urfave/cli.Command.Run(0x64c994, 0x3, 0x0, 0x0, 0xc0000ee020, 0x1, 0x1, 0x650d90, 0xa, 0x0, ...)
I:/VSProject/go/pkg/mod/github.com/urfave/[email protected]/command.go:174 +0x523
github.com/urfave/cli.(*App).Run(0xc0000e8000, 0xc0000044a0, 0x2, 0x2, 0x0, 0x0)
I:/VSProject/go/pkg/mod/github.com/urfave/[email protected]/app.go:276 +0x72c
main.main()
I:/VSProject/go/pkg/mod/github.com/bilibili/[email protected]/tool/kratos/main.go:57 +0x3f7
官方文档解释这个文件也是生成出来的、尝试后,发现go generate可以生成它。
I:\VSProject\go\src\demo\internal\di>go generate
go get -u github.com/google/wire/cmd/wire
go: finding golang.org/x/tools latest
wire: 安装成功!
wire: demo/internal/di: wrote I:\VSProject\go\src\demo\internal\di\wire_gen.go
I:\VSProject\go\src\demo\internal\di>
后来发现其实前面的api路径下的go文件也可以用go generate生成。
go generate
go get -u github.com/bilibili/kratos/tool/kratos-protoc
protoc: 安装成功!
2019/12/24 21:42:31 protoc --proto_path=I:\VSProject\go/src --proto_path=I:\VSProject\go/pkg/mod/github.com/bilibili/[email protected]/third_party --proto_path=I:\VSProject\go\src\demo\api --bm_out=:. api.proto
2019/12/24 21:42:31 protoc --proto_path=I:\VSProject\go/src --proto_path=I:\VSProject\go/pkg/mod/github.com/bilibili/[email protected]/third_party --proto_path=I:\VSProject\go\src\demo\api --gofast_out=plugins=grpc:. api.proto
2019/12/24 21:42:31 generate api.proto success.
I:\VSProject\go\src\demo\api>
回来看两个api下的两个go接口:
api.bm.go
为BM server的对外接口, BM server即blademaster为热度http框架gin的裁剪.去除了gin中不需要的部分逻辑,
api.pb.go
为grpc的对外接口,应该就是生成的protocbuf 文件。
看看熟悉的api.bm.go
// DemoBMServer is the server API for Demo service.
type DemoBMServer interface {
Ping(ctx context.Context, req *google_protobuf1.Empty) (resp *google_protobuf1.Empty, err error)
SayHello(ctx context.Context, req *HelloReq) (resp *google_protobuf1.Empty, err error)
SayHelloURL(ctx context.Context, req *HelloReq) (resp *HelloResp, err error)
}
var DemoSvc DemoBMServer
func demoPing(c *bm.Context) {
p := new(google_protobuf1.Empty)
if err := c.BindWith(p, binding.Default(c.Request.Method, c.Request.Header.Get("Content-Type"))); err != nil {
return
}
resp, err := DemoSvc.Ping(c, p)
c.JSON(resp, err)
}
func demoSayHello(c *bm.Context) {
p := new(HelloReq)
if err := c.BindWith(p, binding.Default(c.Request.Method, c.Request.Header.Get("Content-Type"))); err != nil {
return
}
resp, err := DemoSvc.SayHello(c, p)
c.JSON(resp, err)
}
func demoSayHelloURL(c *bm.Context) {
p := new(HelloReq)
if err := c.BindWith(p, binding.Default(c.Request.Method, c.Request.Header.Get("Content-Type"))); err != nil {
return
}
resp, err := DemoSvc.SayHelloURL(c, p)
c.JSON(resp, err)
}
// RegisterDemoBMServer Register the blademaster route
func RegisterDemoBMServer(e *bm.Engine, server DemoBMServer) {
DemoSvc = server
e.GET("/demo.service.v1.Demo/Ping", demoPing)
e.GET("/demo.service.v1.Demo/SayHello", demoSayHello)
e.GET("/abc/say_hello", demoSayHelloURL)
}
这个文件会生成以bm上下文为参数的三个接口函数,这些三个接口函数分别是在api.proto里面定义的grpc接口
option go_package = "api";
option (gogoproto.goproto_getters_all) = false;
service Demo {
rpc Ping (.google.protobuf.Empty) returns (.google.protobuf.Empty);
rpc SayHello (HelloReq) returns (.google.protobuf.Empty);
rpc SayHelloURL(HelloReq) returns (HelloResp) {
option (google.api.http) = {
get:"/kratos-demo/say_hello"
};
};
}
message HelloReq {
string name = 1 [(gogoproto.moretags)='form:"name" validate:"required"'];
}
message HelloResp {
string Content = 1 [(gogoproto.jsontag) = 'content'];
}
RegisterDemoBMServer()
会将这三个接口函数注册到bm 引擎的路由上。
可以看到生成的三个接口只是对请求的消息做了简单的校验,然后调用service下的service.go 实现这三个的接口业务。
BindWith()
简单看了下 其实就是校验数据格式是否正确。bind.Default()使用默认校验方式 。默认校验方式失败会返回400。
cmd
├── cmd
│ └── main.go # cmd目录为main所在
main 函数路径 整个服务入口
也没干什么, 初始化日志、paladin配置包初始化、初始化依赖和服务、跑个循环等待信号退出。
flag.Parse()
log.Init(nil) // debug flag: log.dir={path}
defer log.Close()
log.Info("demo start")
paladin.Init()
_, closeFunc, err := di.InitApp()
if err != nil {
panic(err)
}
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
closeFunc()
log.Info("demo exit")
time.Sleep(time.Second)
return
case syscall.SIGHUP:
default:
return
}
}
configs
配置目录、demo使用的toml格式。
├── configs # configs为配置文件目录
│ ├── application.toml # 应用的自定义配置文件,可能是一些业务开关如:useABtest = true
│ ├── db.toml # db相关配置
│ ├── grpc.toml # grpc相关配置
│ ├── http.toml # http相关配置
│ ├── memcache.toml # memcache相关配置
│ └── redis.toml # redis相关配置
简单看一个http的
[Server]
addr = "0.0.0.0:8000"
timeout = "1s"
dao
│ ├── dao # dao层,用于数据库、cache、MQ、依赖某业务grpc|http等资源访问
│ │ ├── dao.bts.go
│ │ ├── dao.go
│ │ ├── db.go
│ │ ├── mc.cache.go
│ │ ├── mc.go
│ │ └── redis.go
我目前只了解redis、就看看redis了,是对redis的操作封装。
package dao
import (
"context"
"github.com/bilibili/kratos/pkg/cache/redis"
"github.com/bilibili/kratos/pkg/conf/paladin"
"github.com/bilibili/kratos/pkg/log"
)
func NewRedis() (r *redis.Redis, err error) {
var cfg struct {
Client *redis.Config
}
if err = paladin.Get("redis.toml").UnmarshalTOML(&cfg); err != nil {
return
}
r = redis.NewRedis(cfg.Client)
return
}
func (d *dao) PingRedis(ctx context.Context) (err error) {
if _, err = d.redis.Do(ctx, "SET", "ping", "pong"); err != nil {
log.Error("conn.Set(PING) error(%v)", err)
}
return
}
NewRedis() 返回的的应该是redis的连接池,可通过Get()方法来取条连接,kratos/cache/redis里面对它做了进一步的封装。
type Redis struct {
pool *Pool
conf *Config
}
// Get gets a connection. The application must close the returned connection.
// This method always returns a valid connection so that applications can defer
// error handling to the first use of the connection. If there is an error
// getting an underlying connection, then the connection Err, Do, Send, Flush
// and Receive methods return that error.
func (p *Pool) Get(ctx context.Context) Conn {
c, err := p.Slice.Get(ctx)
if err != nil {
return errorConnection{err}
}
c1, _ := c.(Conn)
return &pooledConnection{p: p, c: c1.WithContext(ctx), rc: c1, now: beginTime}
}
// Close releases the resources used by the pool.
func (p *Pool) Close() error {
return p.Slice.Close()
}
di
│ ├── di # 依赖注入层 采用wire静态分析依赖
│ │ ├── app.go
│ │ ├── wire.go # wire 声明
│ │ └── wire_gen.go # go generate 生成的代码
使用了google wire静态分析依赖,它是golang的一个依赖注入解决的工具,这个工具能够自动生成类的依赖关系。
执行wire命令,会读取到wire.NewSet里面的ProviderSet,通过分析各个函数的参数和返回值,来自行解决依赖,可以生成wire_gen.go
// +build wireinject
// The build tag makes sure the stub is not built in the final build.
package di
import (
pb "demo/api"
"demo/internal/dao"
"demo/internal/server/grpc"
"demo/internal/server/http"
"demo/internal/service"
"github.com/google/wire"
)
var daoProvider = wire.NewSet(dao.New, dao.NewDB, dao.NewRedis, dao.NewMC)
var serviceProvider = wire.NewSet(service.New, wire.Bind(new(pb.DemoServer), new(*service.Service)))
func InitApp() (*App, func(), error) {
panic(wire.Build(daoProvider, serviceProvider, http.New, grpc.New, NewApp))
}
model
│ ├── model # model层,用于声明业务结构体
│ │ └── model.go
package model
// Kratos hello kratos.
type Kratos struct {
Hello string
}
type Article struct {
ID int64
Content string
Author string
}
server
│ ├── server # server层,用于初始化grpc和http server
│ │ ├── grpc # grpc层,用于初始化grpc server和定义method
│ │ │ └── server.go
│ │ └── http # http层,用于初始化http server和声明handler
│ │ └── server.go
var svc pb.DemoServer
// New new a bm server.
func New(s pb.DemoServer) (engine *bm.Engine, err error) {
var (
hc struct {
Server *bm.ServerConfig
}
)
if err = paladin.Get("http.toml").UnmarshalTOML(&hc); err != nil {
if err != paladin.ErrNotExist {
return
}
err = nil
}
svc = s
engine = bm.DefaultServer(hc.Server)
pb.RegisterDemoBMServer(engine, s)
initRouter(engine)
err = engine.Start()
return
}
func initRouter(e *bm.Engine) {
e.Ping(ping)
g := e.Group("/demo")
{
g.GET("/start", howToStart)
}
}
func ping(ctx *bm.Context) {
if _, err := svc.Ping(ctx, nil); err != nil {
log.Error("ping error(%v)", err)
ctx.AbortWithStatus(http.StatusServiceUnavailable)
}
}
// example for http request handler.
func howToStart(c *bm.Context) {
k := &model.Kratos{
Hello: "Golang 大法好 !!!",
}
c.JSON(k, nil)
}
service
│ └── service # service层,用于业务逻辑处理,且为方便http和grpc共用方法,建议入参和出参保持grpc风格,且使用pb文件生成代码
│ └── service.go
如api层所述、简单实现pb定义的几个接口业务逻辑。
// Service service.
type Service struct {
ac *paladin.Map
dao dao.Dao
}
// New new a service and return.
func New(d dao.Dao) (s *Service, err error) {
s = &Service{
ac: &paladin.TOML{},
dao: d,
}
err = paladin.Watch("application.toml", s.ac)
return
}
// SayHello grpc demo func.
func (s *Service) SayHello(ctx context.Context, req *pb.HelloReq) (reply *empty.Empty, err error) {
reply = new(empty.Empty)
fmt.Printf("hello %s", req.Name)
return
}
// SayHelloURL bm demo func.
func (s *Service) SayHelloURL(ctx context.Context, req *pb.HelloReq) (reply *pb.HelloResp, err error) {
reply = &pb.HelloResp{
Content: "hello " + req.Name,
}
fmt.Printf("hello url %s", req.Name)
return
}
// Ping ping the resource.
func (s *Service) Ping(ctx context.Context, e *empty.Empty) (*empty.Empty, error) {
return &empty.Empty{}, s.dao.Ping(ctx)
}
体感整体结构层次分的很清晰,而且kratos 框架本身就包含很多工具,如果使用起来开发,感觉舒适度会更上一层楼,期待用上的那一天。