Kratos 初入门教程

2025年2月14日 226点热度 0人点赞 0条评论
内容目录

Kratos

Kratos 是一个轻量级的微服务框架,使用 Go 语言编写,官方文档地址: https://go-kratos.dev/docs/

Kratos 核心组件图:

file

Kratos 核心组件说明:

  • APIs:协议通信以 HTTP/gRPC 为基础,通过 Protobuf 进行定义;
  • Errors:通过 Protobuf 的 Enum 作为错误码定义,以及工具生成判定接口;
  • Metadata:在协议通信 HTTP/gRPC 中,通过 Middleware 规范化服务元信息传递;
  • Config:支持多数据源方式,进行配置合并铺平,通过 Atomic 方式支持动态配置;
  • Logger:标准日志接口,可方便集成三方 log 库,并可通过 fluentd 收集日志;
  • Metrics:统一指标接口,可以实现各种指标系统,默认集成 Prometheus;
  • Tracing:遵循 OpenTelemetry 规范定义,以实现微服务链路追踪;
  • Encoding:支持 Accept 和 Content-Type 进行自动选择内容编码;
  • Transport:通用的 HTTP/gRPC 传输层,实现统一的 Middleware 插件支持;
  • Registry:实现统一注册中心接口,可插件化对接各种注册中心;

下面讲解如何安装 kratos 开发环境。

建议开启 GO111MODULE,以便规范包引用。

go env -w GO111MODULE=on

执行命令安装 kratos 配套的 cli 工具,在开发的各个阶段都需要 kratos 命令行工具的参与:

go install github.com/go-kratos/kratos/cmd/kratos/v2@latest

然后通过 kratos 模板项目:

kratos new helloworld

模板项目结构如下(去掉了文件,只列出目录):

  .
├── Dockerfile  
├── LICENSE
├── Makefile  
├── README.md
├── api // 下面维护了微服务使用的proto文件以及根据它们所生成的go文件
│   └── helloworld
│       └── v1
├── cmd  // 整个项目启动的入口文件
│   └── server
├── configs  // 这里通常维护一些本地调试用的样例配置文件
│   └── config.yaml
├── generate.go
├── go.mod
├── go.sum
├── internal  // 该服务所有不对外暴露的代码
│   ├── biz   // 业务逻辑的组装层
│   ├── conf  // 内部使用的config的结构定义,使用proto格式生成
│   ├── data  // 业务数据访问
│   ├── server  // http和grpc实例的创建和配置
│   └── service  // 实现了 api 定义的服务层
└── third_party  // api 依赖的第三方proto
    ├── README.md
    ├── google
    │   └── api
    └── validate

执行 kratos run 即可启动项目,默认会监听 8000、9000 端口,配置文件在 conf/config.yaml 中。

添加接口

Kratos 使用 proto 文件设计 grpc、http 接口,甚至模型也使用 proto 生成,proto 文件在 kratos 中具有重要地位,那么记下来我们手动添加一个 proto 文件并编写服务接口,通过案例了解 kratos 的开发流程和工作流程。

执行命令生成一个 demo 的 proto 模板,示例文件会被添加到项目目录中。

kratos proto add api/helloworld/v1/demo.proto

file

生成的 demo.proto 内容如下:

syntax = "proto3";

package api.helloworld.v1;

import "google/api/annotations.proto";

option go_package = "helloworld/api/helloworld/v1;v1";
option java_multiple_files = true;
option java_package = "api.helloworld.v1";

service Demo {
    rpc CreateDemo (CreateDemoRequest) returns (CreateDemoReply);
    rpc UpdateDemo (UpdateDemoRequest) returns (UpdateDemoReply);
    rpc DeleteDemo (DeleteDemoRequest) returns (DeleteDemoReply);
    rpc GetDemo (GetDemoRequest) returns (GetDemoReply);
    rpc ListDemo (ListDemoRequest) returns (ListDemoReply);
}

message CreateDemoRequest {}
message CreateDemoReply {}

message UpdateDemoRequest {}
message UpdateDemoReply {}

message DeleteDemoRequest {}
message DeleteDemoReply {}

message GetDemoRequest {}
message GetDemoReply {}

message ListDemoRequest {}
message ListDemoReply {}

编译 demo.proto 文件,会在相同目录生成 demo.pb.go,里面包含了基础接口、请求参数返回参数、客户端实现等,后续实现 grpc、http 服务时,都要基于此文件继承实现。执行编译命令:

kratos proto client api/helloworld/v1/demo.proto

file

demo.pb.go 文件有个默认的空实现,后续生成 http 接口代码和 grpc 接口代码时,都要继承该类型:

type UnimplementedDemoServer struct{}

UnimplementedDemoServer 定义如下:

file

如果在 proto 中声明了 http 属性,那么还会同时生成 demo_http.pb.go

例如将 CreateDemo 方法改成:

    rpc CreateDemo (CreateDemoRequest) returns (CreateDemoReply) {
        option (google.api.http) = {
            get: "/demo/create"
        };
    }

file

生成的 demo.pb.go 包括了接口定义、模型、序列化反序列化 等,grpc、http 两个代码文件则生成了 client 远程调用代码。

现在,继续为 demo.proto 生成 api 服务代码,生成的接口文件在 internal/service 里面。

kratos proto server api/helloworld/v1/demo.proto -t internal/service

file

在服务接口文件 internal/service.demo.go 中,其结构体定义如下:

type DemoService struct {
    pb.UnimplementedDemoServer
}

DemoService 继承了 pb.UnimplementedDemoServer,不过返回值都是空的,没意义。

因为 internal/service.demo.go 是 API 接口,不能直接写业务代码,所以后续还需要编写业务服务类 DemoService,实现的代码要放到 internal/biz 下。

internal/service
   ↓ ↓ ↓
internal/biz

internal/service 用于实现 api 目录下的接口定义,实现了 api 定义的服务层,类似 DDD 的 application 层,处理 DTO 到 biz 领域实体的转换(DTO -> DO),同时协同各类 biz 交互,但是不应处理复杂逻辑。

internal/biz 则是业务逻辑的组装层,类似 DDD 的 domain 层,data 类似 DDD 的 repo,而 repo 接口在这里定义,使用依赖倒置的原则。

internal/service
   ↓ ↓ ↓
internal/biz
   ↓ ↓ ↓
internal/data

注册接口

虽然生成了 demo 的 grpc 和 http 接口,但是接口服务还没有被添加到 grpc、http 服务中,项目启动后是不可访问的。

首先定义如何实例化 DemoService,在 internal/service/demo.go 中,可以看到一个 NewDemoService 函数,这是 wire 框架的写法,用来实例化一个服务。

func NewDemoService() *DemoService {
    return &DemoService{}
}

打开 internal/service/service.go 使用 wire 注册 DemoService 服务提供器,

把原来的代码改成:

var ProviderSet = wire.NewSet(NewGreeterService,NewDemoService)

然后再执行命令构建生成:

make generate

internal/server 的 http.go 和 grpc.go 中注册 DemoService 的 grpc 服务和 http 服务,提供远程访问服务接口,添加到函数参数中:

// NewGRPCServer new a gRPC server.
func NewGRPCServer(
    c *conf.Server,
    greeter *service.GreeterService,
    demo *service.DemoService,
    logger log.Logger) *grpc.Server {
    var opts = []grpc.ServerOption{
        grpc.Middleware(
            recovery.Recovery(),
        ),
    }
    if c.Grpc.Network != "" {
        opts = append(opts, grpc.Network(c.Grpc.Network))
    }
    if c.Grpc.Addr != "" {
        opts = append(opts, grpc.Address(c.Grpc.Addr))
    }
    if c.Grpc.Timeout != nil {
        opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration()))
    }
    srv := grpc.NewServer(opts...)
    v1.RegisterGreeterServer(srv, greeter)
    v1.RegisterDemoServer(srv, demo)
    return srv
}

file

同样,注册到 http 服务中:

// NewHTTPServer new an HTTP server.
func NewHTTPServer(c *conf.Server, 
    greeter *service.GreeterService,
    demo *service.DemoService,
    logger log.Logger) *http.Server {
    var opts = []http.ServerOption{
        http.Middleware(
            recovery.Recovery(),
        ),
    }
    if c.Http.Network != "" {
        opts = append(opts, http.Network(c.Http.Network))
    }
    if c.Http.Addr != "" {
        opts = append(opts, http.Address(c.Http.Addr))
    }
    if c.Http.Timeout != nil {
        opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
    }
    srv := http.NewServer(opts...)
    v1.RegisterGreeterHTTPServer(srv, greeter)
    v1.RegisterDemoHTTPServer(srv, demo)
    return srv
}

file

然后执行 make generate ,以便能够让 wire 生成最新的代码。

最后调用 kratos run 启动项目。

file

swagger 生成和调试

因为 kratos 是通过 proto 协作的,而在 go 中根据代码生成 swagger 又极为麻烦,因此 kratos 的思路是通过 proto 统一写作,然后生成代码以及转换为 swagger。

如果使用 kratos-layout 模板,直接使用 make api 命令即可。

如果是自行搭建的,则安装对应的支持插件,参考:https://go-kratos.dev/docs/guide/openapi

go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest

然后执行命令生成某个 proto 文件到 openapi.yaml 中:

protoc --proto_path=. --proto_path=./third_party --openapi_out=fq_schema_naming=true,default_response=false:. api/helloworld/v1/greeter.proto

如果执行 make api 报错,如下,可能是因为 Windows 使用 bash 命令时跟 git 自带的冲突了。

/usr/bin/sh: line 1: E:/: Is a directory
protoc --proto_path=./api \
       --proto_path=./third_party \
               --go_out=paths=source_relative:./api \
               --go-http_out=paths=source_relative:./api \
               --go-grpc_out=paths=source_relative:./api \
       --openapi_out=fq_schema_naming=true,default_response=false:. \

Missing input file.
make: *** [Makefile:39: api] Error 1

执行 find api -name *.proto 命令,可以看到不正确:

file

在子系统下(Linux)可以执行是可以的。

file

进入 git-bash 目录,没有 find 程序,因为调用的是 windows 本身的 find 程序,所以该该命令无效。

打开 git-bash,执行 where 命令,即使使用 git-bash,也会因为优先使用 windows 的 find 命令,而导致命令格式错误:

$where find
C:\Windows\System32\find.exe
E:\Program Files\Git\usr\bin\find.exe

file

这种情况建议检查 git 安装路径是否带有空格、中文,然后卸载重新安装,并且安装时选择 Add a Git Bash Profle to Windows Termina

file

如果使用 goland 直接运行 Makefile,别忘记了设置 make 位置。

file

为例那个在项目里面直接使用 Swagger 调试,我们需要为示例服务添加 swagger-ui 支持,添加依赖包:

go get -u github.com/go-kratos/swagger-api

修改 internal/server/http.go 文件,添加 swagger 路由代码,路由位置尽量靠前,以免被其它路由地址冲掉:

openAPIhandler := openapiv2.NewHandler()
srv.HandlePrefix("/q/", openAPIhandler)

file

打开 http://localhost:8000/q/swagger-ui ,可以看到 swagger 页面,包括 GRPC 也可以在页面调试。

file

创建业务逻辑 biz

Fratos 的需要在 internal/biz 目录中定义业务操作代码,然后提供给 internal/demo/DemoService 中使用,过程比较麻烦,并且不能直接使用,需要代理一层。

这里要使用依赖倒置原则,也就是 internal/biz 需要定义数据持久化接口,然后通过接口实现对数据的增删查改操作,然后在 internal/data 实现具体的细节,internal/biz 不直接调用 internal/data 的服务,这两者是解耦的,由 wire 注入服务。

为了方便,这里以 Greeter 为例,首先 internal/biz 定义操作数据库的接口,该接口将会被 internal/data 目录中的代码实现:

// GreeterRepo is a Greater repo.
type GreeterRepo interface {
    Save(context.Context, *Greeter) (*Greeter, error)
    Update(context.Context, *Greeter) (*Greeter, error)
    FindByID(context.Context, int64) (*Greeter, error)
    ListByHello(context.Context, string) ([]*Greeter, error)
    ListAll(context.Context) ([]*Greeter, error)
}

其中还需要在该文件中定义请求参数、响应参数等,例如 Greeter,就是一个结构体参数模型。

// Greeter is a Greeter model.
type Greeter struct {
    Hello string
}

然后编写业务逻辑服务 GreeterUsecase, internal/service 的 API 层会调用业务服务接口对外提供服务。

// GreeterUsecase is a Greeter usecase.
type GreeterUsecase struct {
    repo GreeterRepo
    log  *log.Helper
}

// NewGreeterUsecase new a Greeter usecase.
func NewGreeterUsecase(repo GreeterRepo, logger log.Logger) *GreeterUsecase {
    return &GreeterUsecase{repo: repo, log: log.NewHelper(logger)}
}

// 业务代码
func (uc *GreeterUsecase) CreateGreeter(ctx context.Context, g *Greeter) (*Greeter, error) {
    uc.log.WithContext(ctx).Infof("CreateGreeter: %v", g.Hello)
    return uc.repo.Save(ctx, g)
}

接着修改 service/greeter.go,在 API 层注入 GreeterUsecase 服务。

// GreeterService is a greeter service.
type GreeterService struct {
    v1.UnimplementedGreeterServer

    uc *biz.GreeterUsecase
}

biz 的代码不能直接操作数据库数据,而是使用 internal/data 的仓储代码操作数据库,其中需要在 biz 中定义操作数据操作接口,业务代码不需要关注数据是在 redis 还是 mysql 等,只需要通过接口解耦即可。

file

如何在 Goland 中启动调试

前面都是使用 kratos run 启动项目,接下来讲解如何在 Goland 中调试项目。
首先创建配置,添加 “Go 构建”,假设项目名称是根据模板创建的,名称为 helloworld,配置示例如下图所示。

file

  • 运行种类:软件包
  • 工作目录,选择项目所在目录
  • Go 工具参数:-i
  • 软件包目录:导航到 cmd/helloworld,注意前面带上项目目录名称
  • 程序实参:-conf ./configs 导入本地配置文件。

痴者工良

高级程序员劝退师

文章评论