[toc]html
上篇文章go微服務框架kratos學習筆記一(kratos demo)跑了kratos demogit
本章來看看demo項目的總體結構。github
目錄結構golang
├─api # 對外接口 ├─cmd # main ├─configs # 配置 ├─internal │ ├─dao #數據訪問 │ ├─di #依賴注入 │ ├─model #業務結構體的聲明 │ ├─server #grpc、http初始化 │ │ ├─grpc │ │ └─http │ └─service #業務邏輯處理 └─test
官方文檔解釋redis
├── 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應該是最複雜的部分,其餘的都很好看懂。docker
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/kratos@v0.3.2-0.20191216053608-e8e05452b3b0/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/kratos@v0.3.2-0.20191216053608-e8e05452b3b0/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中不須要的部分邏輯,json
api目錄主要爲對外目錄、
api.bm.go
、apb.pb.go
能夠經過kartos tool生成(kratos tool能夠基於proto生成http&grpc代碼,生成緩存回源代碼,生成memcache執行代碼,生成swagger文檔等工具集) bm、 pb 分別爲http和grpc的接口。api
像上篇文章,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/kratos@v0.3.2-0.20191216053608-e8e05452b3b0/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/kratos@v0.3.2-0.20191216053608-e8e05452b3b0/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/kratos@v0.3.2-0.20191216053608-e8e05452b3b0/tool/kratos/run.go:25 +0x36e github.com/urfave/cli.HandleAction(0x603080, 0x65fdc8, 0xc000102160, 0xc000102160, 0x0) I:/VSProject/go/pkg/mod/github.com/urfave/cli@v1.22.2/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/cli@v1.22.2/command.go:174 +0x523 github.com/urfave/cli.(*App).Run(0xc0000e8000, 0xc0000044a0, 0x2, 0x2, 0x0, 0x0) I:/VSProject/go/pkg/mod/github.com/urfave/cli@v1.22.2/app.go:276 +0x72c main.main() I:/VSProject/go/pkg/mod/github.com/bilibili/kratos@v0.3.2-0.20191216053608-e8e05452b3b0/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/kratos@v0.3.2-0.20191224125553-6e1180f53a8e/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/kratos@v0.3.2-0.20191224125553-6e1180f53a8e/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 框架自己就包含不少工具,若是使用起來開發,感受溫馨度會更上一層樓,期待用上的那一天。
原文出處:https://www.cnblogs.com/ailumiyana/p/12094064.html