Kratos Beginner's Guide

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

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:

file

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

file

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

file

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:

file

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";
		};
	}

file

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

file

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
}

file

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
}

file

Then execute make generate so that wire can generate the latest code.

Finally, call kratos run to start the project.

file

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:

file

It can work in subsystems (Linux).

file

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

file

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.

file

For example, directly running the Makefile in the project with Goland, do not forget to set the make location.

file

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)

file

Open http://localhost:8000/q/swagger-ui, and you will see the Swagger page, where you can also debug GRPC.

file

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.

file

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.

file

  • 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.

痴者工良

高级程序员劝退师

文章评论