• go微服务框架kratos学习笔记二(kratos demo 结构)



    上篇文章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.goapb.pb.go 可以通过kartos tool生成(kratos tool可以基于proto生成http&grpc代码,生成缓存回源代码,生成memcache执行代码,生成swagger文档等工具集) bm、 pb 分别为http和grpc的接口。

    C:serversrcgosrcdempapi>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:serversrcgo/src --proto_path=C:serversrcgo/pkg/mod/github.com/bilibili/kratos@v0.3.2-0.20191216053608-e8e05452b3b0/third_party --proto_path=C:serversrcgosrcdempapi --bm_out=:. api.proto
    2019/12/23 17:48:52 protoc --proto_path=C:serversrcgo/src --proto_path=C:serversrcgo/pkg/mod/github.com/bilibili/kratos@v0.3.2-0.20191216053608-e8e05452b3b0/third_party --proto_path=C:serversrcgosrcdempapi --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.goapb.pb.go 可以通过kartos tool生成(kratos tool可以基于proto生成http&grpc代码,生成缓存回源代码,生成memcache执行代码,生成swagger文档等工具集) bm、 pb 分别为http和grpc的接口。

    像上篇文章,protoc 没装 不能运行的情况下,其实可以用kratos tool 来生成 对应go文件的。

    C:serversrcgosrcdempapi>kratos tool protoc --grpc --bm api.proto
    I:.
        api.proto
        client.go
    
    没有子文件夹
    
    I:VSProjectgosrcdemoapi>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:VSProjectgo/src --proto_path=I:VSProjectgo/pkg/mod/github.com/bilibili/kratos@v0.3.2-0.20191216053608-e8e05452b3b0/third_party --proto_path=I:VSProjectgosrcdemoapi --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:VSProjectgo/src --proto_path=I:VSProjectgo/pkg/mod/github.com/bilibili/kratos@v0.3.2-0.20191216053608-e8e05452b3b0/third_party --proto_path=I:VSProjectgosrcdemoapi --gofast_out=plugins=grpc:. api.proto
    2019/12/24 21:13:47 generate api.proto success.
    
    I:VSProjectgosrcdemoapi>tree /f
    I:.
        api.bm.go
        api.pb.go
        api.proto
        client.go
    
    

    但这样还是不够运行,错误是缺少di.InitApp(), 对比上次笔记(一)的正常项目,会发现还少了一个wire_gen.go文件

    I:VSProjectgosrcdemointernaldi>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:VSProjectgosrcdemointernaldi>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:VSProjectgosrcdemointernaldiwire_gen.go
    
    I:VSProjectgosrcdemointernaldi>
    

    后来发现其实前面的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:VSProjectgo/src --proto_path=I:VSProjectgo/pkg/mod/github.com/bilibili/kratos@v0.3.2-0.20191224125553-6e1180f53a8e/third_party --proto_path=I:VSProjectgosrcdemoapi --bm_out=:. api.proto
    2019/12/24 21:42:31 protoc --proto_path=I:VSProjectgo/src --proto_path=I:VSProjectgo/pkg/mod/github.com/bilibili/kratos@v0.3.2-0.20191224125553-6e1180f53a8e/third_party --proto_path=I:VSProjectgosrcdemoapi --gofast_out=plugins=grpc:. api.proto
    2019/12/24 21:42:31 generate api.proto success.
    
    I:VSProjectgosrcdemoapi>
    

    回来看两个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 框架本身就包含很多工具,如果使用起来开发,感觉舒适度会更上一层楼,期待用上的那一天。

  • 相关阅读:
    create-react-app搭建的项目中添加bootstrap
    用es6的Array.reduce()方法计算一个字符串中每个字符出现的次数
    为Docker配置阿里加速器,系统为Debian8
    基于Spring Boot,使用JPA动态调用Sql查询数据
    基于Spring Boot,使用JPA调用Sql Server数据库的存储过程并返回记录集合
    基于Spring Boot,使用JPA操作Sql Server数据库完成CRUD
    ES6,Array.includes()函数的用法
    【编程风格】c++命名约定
    【转】吴恩达的视频课程做成了文字版 ~~~
    【专业学习】常用的技术网站
  • 原文地址:https://www.cnblogs.com/ailumiyana/p/12094064.html
Copyright © 2020-2023  润新知