go微服務框架kratos學習筆記二(kratos demo 結構)

[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.goapb.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.goapb.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

相關文章
相關標籤/搜索