Kratos
Kratos is a lightweight microservice framework written in Go. The official documentation can be found at: https://go-kratos.dev/docs/
Kratos core component diagram:
Description of Kratos core components:
- APIs: Protocol communication is based on HTTP/gRPC, defined using Protobuf;
- Errors: Uses Protobuf's Enum for error code definition and generates determination interfaces with tools;
- Metadata: Standardizes service metadata transmission in protocol communication (HTTP/gRPC) through Middleware;
- Config: Supports multiple data source methods for configuration merging and paving; supports dynamic configuration through Atomic methods;
- Logger: Standard logging interface that integrates easily with third-party logging libraries, and supports log collection via fluentd;
- Metrics: Unified metrics interface that can implement various metrics systems, with Prometheus integrated by default;
- Tracing: Follows OpenTelemetry standards for microservice link tracing;
- Encoding: Supports automatic selection of content encoding based on Accept and Content-Type;
- Transport: A universal HTTP/gRPC transport layer that implements unified Middleware plugin support;
- Registry: Implements a unified registration center interface, capable of plugging in various registration centers;
Next, let's explain how to install the Kratos development environment.
It is recommended to enable GO111MODULE to standardize package references.
go env -w GO111MODULE=on
Execute the command to install the accompanying CLI tools for Kratos, which are needed at various stages of development:
go install github.com/go-kratos/kratos/cmd/kratos/v2@latest
Then create a new Kratos template project:
kratos new helloworld
The structure of the template project is as follows (files omitted, only directories listed):
.
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── api // Maintains the proto files used by the microservice and the generated go files based on them
│ └── helloworld
│ └── v1
├── cmd // The entry file for starting the entire project
│ └── server
├── configs // Typically maintains sample configuration files for local debugging
│ └── config.yaml
├── generate.go
├── go.mod
├── go.sum
├── internal // All code that is not exposed externally for this service
│ ├── biz // Business logic assembly layer
│ ├── conf // Structure definition of internal config, generated in proto format
│ ├── data // Accessing business data
│ ├── server // Creation and configuration of HTTP and gRPC instances
│ └── service // Implements the service layer defined by the API
└── third_party // Third-party proto dependencies required by the API
├── README.md
├── google
│ └── api
└── validate
Execute kratos run
to start the project. By default, it will listen on ports 8000 and 9000, and the configuration file is located at conf/config.yaml
.
Adding Interfaces
Kratos uses proto files to design gRPC and HTTP interfaces, with models also generated using proto. Therefore, the proto files hold significant importance in Kratos. Let's manually add a proto file and write service interfaces, using this example to understand the development and workflow of Kratos.
Execute the command to generate a demo proto template; the sample file will be added to the project directory.
kratos proto add api/helloworld/v1/demo.proto
The contents of the generated demo.proto
are as follows:
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 {}
Compile the demo.proto
file, which will generate demo.pb.go
in the same directory. This file includes the basic interfaces, request/response parameters, client implementation, and so on. Future implementations of gRPC and HTTP services will need to extend from this file. Execute the compilation command:
kratos proto client api/helloworld/v1/demo.proto
The demo.pb.go
file has a default empty implementation, and both the generated HTTP and gRPC interface codes will inherit this type:
type UnimplementedDemoServer struct{}
The definition of UnimplementedDemoServer
is as follows:
If an HTTP attribute is declared in the proto, a demo_http.pb.go
will also be generated simultaneously.
For example, change the CreateDemo
method to:
rpc CreateDemo (CreateDemoRequest) returns (CreateDemoReply) {
option (google.api.http) = {
get: "/demo/create";
};
}
The generated demo.pb.go
includes interface definitions, models, serialization, and deserialization, while the gRPC and HTTP code files generate client remote call codes.
Now, continue to generate API service code for demo.proto
, with the generated interface files located in internal/service
.
kratos proto server api/helloworld/v1/demo.proto -t internal/service
In the service interface file internal/service/demo.go
, its struct definition is as follows:
type DemoService struct {
pb.UnimplementedDemoServer
}
DemoService
inherits from pb.UnimplementedDemoServer
, but the return values are all empty, rendering them meaningless.
Since internal/service/demo.go
is the API interface, business logic cannot be directly implemented there. Therefore, a business service class named DemoService
needs to be written, with the implementation code placed in internal/biz
.
internal/service
↓ ↓ ↓
internal/biz
internal/service
is used to implement the interface definitions under the api
directory, acting as the service layer that implements the API definitions, similar to the application layer in DDD, which handles the transformation from DTO to biz entity (DTO -> DO), while collaborating with various biz interactions, but should not handle complex logic.
internal/biz
, on the other hand, is the assembly layer for business logic, resembling the domain layer in DDD. The data is similar to DDD's repository, and the repository interfaces are defined here, adhering to the dependency inversion principle.
internal/service
↓ ↓ ↓
internal/biz
↓ ↓ ↓
internal/data
Registering Interfaces
Although gRPC and HTTP interfaces for demo
have been generated, the interface services have not been added to gRPC and HTTP services, making them inaccessible once the project starts.
First, define how to instantiate DemoService
. In internal/service/demo.go
, there is a NewDemoService
function, which is the wire framework's way of instantiating a service.
func NewDemoService() *DemoService {
return &DemoService{}
}
Open internal/service/service.go
and use wire to register the DemoService
provider:
Change the original code to:
var ProviderSet = wire.NewSet(NewGreeterService, NewDemoService)
Then execute the build command to generate:
make generate
In internal/server
, register DemoService
's gRPC server and HTTP server in http.go
and grpc.go
to provide remote access service interfaces. Add them as function parameters:
// 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
}
Similarly, register it in the HTTP server:
// 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
}
Then execute make generate
so that wire can generate the latest code.
Finally, call kratos run
to start the project.
Swagger Generation and Debugging
Since Kratos cooperates through proto, generating swagger based on code in Go is particularly troublesome. Therefore, Kratos's approach is to unify writing through proto, then generate code and convert it to swagger.
If you are using the kratos-layout template, you can directly use the make api
command.
If you are setting up on your own, then install the corresponding support plugins, as referenced in: https://go-kratos.dev/docs/guide/openapi
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest
Then execute the command to generate a proto file into openapi.yaml:
protoc --proto_path=. --proto_path=./third_party --openapi_out=fq_schema_naming=true,default_response=false:. api/helloworld/v1/greeter.proto
If executing make api
results in an error, such as below, it may be because using bash commands on Windows conflicts with Git's built-in features.
/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
Executing find api -name *.proto
can show that it's incorrect:
It can work in subsystems (Linux).
Entering the git-bash directory, the find program is absent because it defaults to the Windows version of find, which renders that command ineffective.
Open git-bash and execute the where command; even when using git-bash, Windows's find command takes priority, causing command formatting errors:
$where find
C:\Windows\System32\find.exe
E:\Program Files\Git\usr\bin\find.exe
In this case, it is suggested to check if the installation path of Git contains spaces or Chinese characters, then uninstall and reinstall, ensuring to select "Add a Git Bash Profile to Windows Terminal" during installation.
For example, directly running the Makefile in the project with Goland, do not forget to set the make location.
To debug using Swagger directly in the project, we need to add Swagger-UI support for the sample service by adding a dependency package:
go get -u github.com/go-kratos/swagger-api
Modify the internal/server/http.go
file to add Swagger router code; the route position should be as early as possible to avoid being overridden by other route addresses:
openAPIhandler := openapiv2.NewHandler()
srv.HandlePrefix("/q/", openAPIhandler)
Open http://localhost:8000/q/swagger-ui, and you will see the Swagger page, where you can also debug GRPC.
。
Create Business Logic biz
Fratos needs to define business operation codes in the internal/biz
directory, which will then be provided to be used in internal/demo/DemoService
. The process is somewhat cumbersome, and it cannot be used directly; a layer of proxy is needed.
Here, we need to use the Dependency Inversion Principle, which means that internal/biz
needs to define data persistence interfaces, and then implement CRUD (Create, Read, Update, Delete) operations through these interfaces. The specific details will be implemented in internal/data
, and internal/biz
does not directly call services from internal/data
. These two are decoupled and services are injected through wire.
For convenience, we take Greeter as an example. First, internal/biz
defines the interface for database operations, which will be implemented by code in the internal/data
directory:
// GreeterRepo is a Greeter 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)
}
In this file, we also need to define request parameters, response parameters, etc. For instance, Greeter is a struct parameter model.
// Greeter is a Greeter model.
type Greeter struct {
Hello string
}
Next, we write the business logic service GreeterUsecase. The API layer in internal/service
will call the business service interface to provide services externally.
// GreeterUsecase is a Greeter usecase.
type GreeterUsecase struct {
repo GreeterRepo
log *log.Helper
}
// NewGreeterUsecase creates a new Greeter usecase.
func NewGreeterUsecase(repo GreeterRepo, logger log.Logger) *GreeterUsecase {
return &GreeterUsecase{repo: repo, log: log.NewHelper(logger)}
}
// Business code
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)
}
Then modify service/greeter.go
to inject the GreeterUsecase service in the API layer.
// GreeterService is a Greeter service.
type GreeterService struct {
v1.UnimplementedGreeterServer
uc *biz.GreeterUsecase
}
The biz code cannot directly operate on database data; instead, it uses the repository code from internal/data
to operate the database. Here, it is necessary to define data operation interfaces in biz, and the business code does not need to be concerned about whether the data is in Redis or MySQL, etc. It only needs to be decoupled through the interface.
How to Start Debugging in Goland
Previously, we used kratos run
to start the project. Next, we will explain how to debug the project in Goland.
First, create a configuration and add a "Go Build." Assuming the project name is created based on the template and is named helloworld, the configuration example is shown in the picture below.
- Run type: Package
- Working directory: Select the directory where the project resides
- Go tool parameters:
-i
- Package directory: Navigate to
cmd/helloworld
, note the project directory name at the beginning - Program arguments:
-conf ./configs
to import the local configuration file.
文章评论