Kratos是一个Go语言实现的微服务框架,说得更准确一点,它更类似于一个使用Go构建微服务的工具箱,开发者可以按照自己的习惯选用或定制其中的组件,来打造自己的微服务。

kratos结构
.
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── api // 下面维护了微服务使用的proto文件以及根据它们所生成的go文件
│ └── helloworld
│ └── v1
│ ├── error_reason.pb.go
│ ├── error_reason.proto
│ ├── error_reason.swagger.json
│ ├── greeter.pb.go
│ ├── greeter.proto
│ ├── greeter.swagger.json
│ ├── greeter_grpc.pb.go
│ └── greeter_http.pb.go
├── cmd // 整个项目启动的入口文件
│ └── server
│ ├── main.go
│ ├── wire.go // 我们使用wire来维护依赖注入
│ └── wire_gen.go
├── configs // 这里通常维护一些本地调试用的样例配置文件
│ └── config.yaml
├── generate.go
├── go.mod
├── go.sum
├── internal // 该服务所有不对外暴露的代码,通常的业务逻辑都在这下面,使用internal避免错误引用
│ ├── biz // 业务逻辑的组装层,类似 DDD 的 domain 层,data 类似 DDD 的 repo,而 repo 接口在这里定义,使用依赖倒置的原则。
│ │ ├── README.md
│ │ ├── biz.go
│ │ └── greeter.go
│ ├── conf // 内部使用的config的结构定义,使用proto格式生成
│ │ ├── conf.pb.go
│ │ └── conf.proto
│ ├── data // 业务数据访问,包含 cache、db 等封装,实现了 biz 的 repo 接口。我们可能会把 data 与 dao 混淆在一起,data 偏重业务的含义,它所要做的是将领域对象重新拿出来,我们去掉了 DDD 的 infra层。
│ │ ├── README.md
│ │ ├── data.go
│ │ └── greeter.go
│ ├── server // http和grpc实例的创建和配置
│ │ ├── grpc.go
│ │ ├── http.go
│ │ └── server.go
│ └── service // 实现了 api 定义的服务层,类似 DDD 的 application 层,处理 DTO 到 biz 领域实体的转换(DTO -> DO),同时协同各类 biz 交互,但是不应处理复杂逻辑
│ ├── README.md
│ ├── greeter.go
│ └── service.go
└── third_party // api 依赖的第三方proto
├── README.md
├── google
│ └── api
│ ├── annotations.proto
│ ├── http.proto
│ └── httpbody.proto
└── validate
├── README.md
└── validate.proto
kratos 是与 Kratos 框架配套的脚手架工具,kratos 能够
- 通过模板快速创建项目
- 快速创建与生成 protoc 文件
- 使用开发过程中常用的命令
- 极大提高开发效率,减轻心智负担
因此我们可以通过kratos创建模板后在以此修改protoc,生成我们需要的代码。
当我们创建一个新的项目,此时可以
kratos new bubble
在这之后我们创建自己的protobuf文件,
kratos proto add api/bubble/v1/todo.proto
之后根据自己的业务修改todo.proto
syntax = "proto3";
package api.bubble.v1;
import "google/api/annotations.proto"; //添加HTTP注解需要导⼊入 google/api/annotations.proto
option go_package = "bubble/api/bubble/v1;v1";
option java_multiple_files = true;
option java_package = "api.bubble.v1";
service Todo {
rpc CreateTodo (CreateTodoRequest) returns (CreateTodoReply){
option (google.api.http) = {
post: "v1/todo",
body :"*"
};
};
rpc UpdateTodo (UpdateTodoRequest) returns (UpdateTodoReply){
option (google.api.http) = {
put: "v1/todo/{id}",
body :"*"
};
};
rpc DeleteTodo (DeleteTodoRequest) returns (DeleteTodoReply){
option (google.api.http) = {
delete: "v1/todo{id}",
};
};
rpc GetTodo (GetTodoRequest) returns (GetTodoReply){
option (google.api.http) = {
get: "v1/todo/{id}",
};
};
rpc ListTodo (ListTodoRequest) returns (ListTodoReply){
option (google.api.http) = {
get: "v1/todos",
};
};
}
message todo{
int64 id = 1;
string title = 2;
bool status = 3;
}
message CreateTodoRequest {
string title = 1;
}
message CreateTodoReply {
int64 id = 1;
string title = 2;
bool status = 3;
}
message UpdateTodoRequest {
int64 id = 1;
string title = 2;
bool status = 3;
}
message UpdateTodoReply {
}
message DeleteTodoRequest {
int64 id = 1;
}
message DeleteTodoReply {}
message GetTodoRequest {
int64 id = 1;
}
message GetTodoReply {
todo todo = 1;
}
message ListTodoRequest {}
message ListTodoReply {
repeated todo data = 1;
}
之后生成代码
//生成客户端代码
kratos proto client api/bubble/v1/todo.proto
//生成服务端代码
kratos proto server api/bubble/v1/todo.proto -t internal/service
生成的服务端代码位于internal/service文件夹下,客户端代码位于api/bubble/v1之下。
// 实现了 api 定义的服务层,但是不应处理复杂逻辑
//可以理解为接收请求,并将调用diz包来处理请求。
//具体的请求都在diz中
type TodoService struct {
pb.UnimplementedTodoServer
}
func NewTodoService() *TodoService {
return &TodoService{}
}
func (s *TodoService) CreateTodo(ctx context.Context, req *pb.CreateTodoRequest) (*pb.CreateTodoReply, error) {
return &pb.CreateTodoReply{}, nil
}
func (s *TodoService) UpdateTodo(ctx context.Context, req *pb.UpdateTodoRequest) (*pb.UpdateTodoReply, error) {
return &pb.UpdateTodoReply{}, nil
}
func (s *TodoService) DeleteTodo(ctx context.Context, req *pb.DeleteTodoRequest) (*pb.DeleteTodoReply, error) {
return &pb.DeleteTodoReply{}, nil
}
func (s *TodoService) GetTodo(ctx context.Context, req *pb.GetTodoRequest) (*pb.GetTodoReply, error) {
return &pb.GetTodoReply{}, nil
}
func (s *TodoService) ListTodo(ctx context.Context, req *pb.ListTodoRequest) (*pb.ListTodoReply, error) {
return &pb.ListTodoReply{}, nil
}
我们从模板生成的示例中可以看到
// GreeterService is a greeter service.
type GreeterService struct {
v1.UnimplementedGreeterServer
uc *biz.GreeterUsecase
}
// NewGreeterService new a greeter service.
func NewGreeterService(uc *biz.GreeterUsecase) *GreeterService {
return &GreeterService{uc: uc}
}
// SayHello implements helloworld.GreeterServer.
func (s *GreeterService) SayHello(ctx context.Context, in *v1.HelloRequest) (*v1.HelloReply, error) {
g, err := s.uc.CreateGreeter(ctx, &biz.Greeter{Hello: in.Name})
if err != nil {
return nil, err
}
return &v1.HelloReply{Message: "Hello " + g.Hello}, nil
}
// CreateGreeter creates a Greeter, and returns the new Greeter.
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->diz->data
//service/todo.go
//service需要调用diz,因此在TodoService 嵌入TodoUsecase
type TodoService struct {
pb.UnimplementedTodoServer
uc *biz.TodoUsecase
}
func NewTodoService(uc *biz.TodoUsecase) *TodoService {
return &TodoService{uc:uc}
}
func (s *TodoService) CreateTodo(ctx context.Context, req *pb.CreateTodoRequest) (*pb.CreateTodoReply, error) {
//请求验证
if len(req.Title) == 0 {
return &pb.CreateTodoReply{}, errors.New("无效title")
}
data, err := s.uc.CreateTodo(ctx, &biz.Todo{Title: req.Title})
if err != nil {
return nil, err
}
return &pb.CreateTodoReply{Id: data.ID, Title: data.Title, Status: data.Status}, nil
}
func (s *TodoService) UpdateTodo(ctx context.Context, req *pb.UpdateTodoRequest) (*pb.UpdateTodoReply, error) {
return &pb.UpdateTodoReply{}, nil
}
func (s *TodoService) DeleteTodo(ctx context.Context, req *pb.DeleteTodoRequest) (*pb.DeleteTodoReply, error) {
return &pb.DeleteTodoReply{}, nil
}
func (s *TodoService) GetTodo(ctx context.Context, req *pb.GetTodoRequest) (*pb.GetTodoReply, error) {
return &pb.GetTodoReply{}, nil
}
func (s *TodoService) ListTodo(ctx context.Context, req *pb.ListTodoRequest) (*pb.ListTodoReply, error) {
return &pb.ListTodoReply{}, nil
}
//diz
// Greeter is a Greeter model.
type Todo struct {
ID int64
Title string
Status bool
}
// TodoRepo is a Todo repo.
type TodoRepo interface {
Save(context.Context, *Todo) (*Todo, error)
Update(context.Context, *Todo) error
FindByID(context.Context, int64) (*Todo, error)
Delete(context.Context, int64) error
ListAll(context.Context) ([]*Todo, error)
}
// TodoUsecase is a Todo usecase.
type TodoUsecase struct {
repo TodoRepo
log *log.Helper
}
// NewGreeterUsecase new a Greeter usecase.
func NewTodoUsecase(repo TodoRepo, logger log.Logger) *TodoUsecase {
return &TodoUsecase{repo: repo, log: log.NewHelper(logger)}
}
// CreateGreeter creates a Greeter, and returns the new Greeter.
func (uc *TodoUsecase) CreateTodo(ctx context.Context, g *Todo) (*Todo, error) {
uc.log.WithContext(ctx).Infof("CreateGreeter: %v", g.Title)
return uc.repo.Save(ctx, g)
}
//data
type todoRepo struct {
data *Data
log *log.Helper
}
// NewGreeterRepo .
func NewTodoRepo(data *Data, logger log.Logger) biz.TodoRepo {
return &todoRepo{
data: data,
log: log.NewHelper(logger),
}
}
func (r *todoRepo) Save(ctx context.Context, t *biz.Todo) (*biz.Todo, error) {
// 实现数据库的操作
fmt.Printf("save: t:%#v\n", t)
return t, nil
}
func (r *todoRepo) Update(ctx context.Context, t *biz.Todo) error {
return nil
}
func (r *todoRepo) FindByID(context.Context, int64) (*biz.Todo, error) {
return nil, nil
}
func (r *todoRepo) Delete(context.Context, int64) error {
return nil
}
func (r *todoRepo) ListAll(context.Context) ([]*biz.Todo, error) {
return nil, nil
}