Kratos
Kratos 是一个轻量级的微服务框架,使用 Go 语言编写,官方文档地址: https://go-kratos.dev/docs/
Kratos 核心组件图:
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
生成的 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
demo.pb.go 文件有个默认的空实现,后续生成 http 接口代码和 grpc 接口代码时,都要继承该类型:
type UnimplementedDemoServer struct{}
UnimplementedDemoServer 定义如下:
如果在 proto 中声明了 http 属性,那么还会同时生成 demo_http.pb.go
。
例如将 CreateDemo 方法改成:
rpc CreateDemo (CreateDemoRequest) returns (CreateDemoReply) {
option (google.api.http) = {
get: "/demo/create"
};
}
生成的 demo.pb.go
包括了接口定义、模型、序列化反序列化 等,grpc、http 两个代码文件则生成了 client 远程调用代码。
现在,继续为 demo.proto 生成 api 服务代码,生成的接口文件在 internal/service
里面。
kratos proto server api/helloworld/v1/demo.proto -t internal/service
在服务接口文件 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
}
同样,注册到 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
}
然后执行 make generate
,以便能够让 wire 生成最新的代码。
最后调用 kratos run
启动项目。
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
命令,可以看到不正确:
在子系统下(Linux)可以执行是可以的。
进入 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
这种情况建议检查 git 安装路径是否带有空格、中文,然后卸载重新安装,并且安装时选择 Add a Git Bash Profle to Windows Termina
。
如果使用 goland 直接运行 Makefile,别忘记了设置 make 位置。
为例那个在项目里面直接使用 Swagger 调试,我们需要为示例服务添加 swagger-ui 支持,添加依赖包:
go get -u github.com/go-kratos/swagger-api
修改 internal/server/http.go
文件,添加 swagger 路由代码,路由位置尽量靠前,以免被其它路由地址冲掉:
openAPIhandler := openapiv2.NewHandler()
srv.HandlePrefix("/q/", openAPIhandler)
打开 http://localhost:8000/q/swagger-ui ,可以看到 swagger 页面,包括 GRPC 也可以在页面调试。
创建业务逻辑 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 等,只需要通过接口解耦即可。
如何在 Goland 中启动调试
前面都是使用 kratos run
启动项目,接下来讲解如何在 Goland 中调试项目。
首先创建配置,添加 “Go 构建”,假设项目名称是根据模板创建的,名称为 helloworld,配置示例如下图所示。
- 运行种类:软件包
- 工作目录,选择项目所在目录
- Go 工具参数:
-i
- 软件包目录:导航到
cmd/helloworld
,注意前面带上项目目录名称 - 程序实参:
-conf ./configs
导入本地配置文件。
文章评论