Kratos Study Notes - A Simple Analysis of How the Application Runs via Layout
0X01 Exploring Kratos Operation Principles via Layout (kratos v2.0.0-beta4)
Section titled “0X01 Exploring Kratos Operation Principles via Layout (kratos v2.0.0-beta4)”Create Project
Section titled “Create Project”First, install the necessary dependencies and tools:
- go
- protoc
- protoc-gen-go
# Create project templatekratos new helloworld
cd helloworld# Pull project dependenciesgo mod download# Generate proto templatekratos proto add api/helloworld/v1/helloworld.proto# Generate proto source codekratos proto client api/helloworld/v1/helloworld.proto# Generate server templatekratos proto server api/helloworld/v1/helloworld.proto -t internal/serviceAfter executing the commands, a service project will be generated in the current directory. The project structure is as follows. For detailed project structure description, please refer to layout
Run Project
Section titled “Run Project”# Generate all proto source code, wire, etc.go generate ./...
# Compile into executablego build -o ./bin/ ./...
# Run project./bin/helloworld -conf ./configsIf you see the following output, it indicates the project started normally.
level=INFO module=app service_id=7114ad8a-b3bf-11eb-a1b9-f0189850d2cb service_name= version=level=INFO module=transport/grpc msg=[gRPC] server listening on: [::]:9000level=INFO module=transport/http msg=[HTTP] server listening on: [::]:8000Test the interface:
curl 'http://127.0.0.1:8000/helloworld/krtaos'
Output:{ "message": "Hello kratos"}How Does the Application Run?
Section titled “How Does the Application Run?”
Through the above diagram👆, we can intuitively observe the application’s call chain. Simplified, the process is as shown below👇
1. Inject Dependencies and Call newApp() Method
Section titled “1. Inject Dependencies and Call newApp() Method”func main() { flag.Parse() logger := log.NewStdLogger(os.Stdout)
// Call go-kratos/kratos/v2/config, create config instance, and specify source and configuration parsing method c := config.New( config.WithSource( file.NewSource(flagconf), ), config.WithDecoder(func(kv *config.KeyValue, v map[string]interface{}) error { return yaml.Unmarshal(kv.Value, v) }), ) if err := c.Load(); err != nil { panic(err) }
// Scan configuration into the conf struct declared via proto var bc conf.Bootstrap if err := c.Scan(&bc); err != nil { panic(err) }
// Inject dependencies via wire and call newApp method app, cleanup, err := initApp(bc.Server, bc.Data, logger) if err != nil { panic(err) } // Code omitted...}2. Create Kratos Instance
Section titled “2. Create Kratos Instance”In the project’s main.go newApp() method, the kratos.New() method from go-kratos/kratos/v2/app.go is called.
func newApp(logger log.Logger, hs *http.Server, gs *grpc.Server) *kratos.App { return kratos.New( // Configure application kratos.Name(Name), kratos.Version(Version), kratos.Metadata(map[string]string{}), kratos.Logger(logger), // http/grpc services passed via kratos.Server() will be converted to registry.ServiceInstance struct* via buildInstance() kratos.Server( hs, gs, ), )}This method returns an App struct, containing Run() and Stop() methods.
type App struct { opts options // Configuration ctx context.Context // Context cancel func() // Context cancellation method instance *registry.ServiceInstance // Instance declared via kratos.Server(), converted via buildInstance() to *registry.ServiceInstance struct log *log.Helper // Log}
// Run executes all OnStart hooks registered with the application's Lifecycle.func (a *App) Run() error { // Code omitted...}
// Stop gracefully stops the application.func (a *App) Stop() error { // Code omitted...}3. Call Run() Method
Section titled “3. Call Run() Method”The project calls the Run() method of kratos.App struct in the main method.
// Code omitted...// Start Kratosif err := app.Run(); err != nil { panic(err)}Implementation details of the Run() method:
func (a *App) Run() error { a.log.Infow( "service_id", a.opts.id, "service_name", a.opts.name, "version", a.opts.version, ) g, ctx := errgroup.WithContext(a.ctx) // Iterate through service instances declared via kratos.Server() for _, srv := range a.opts.servers { srv := srv // Execute two goroutines to handle service startup and shutdown g.Go(func() error { <-ctx.Done() // Block, wait for cancel method call return srv.Stop() // After goroutine exits, call instance's stop method }) g.Go(func() error { return srv.Start() // Call instance's run method }) } // Check if kratos.Registrar() is configured for service discovery registry if a.opts.registrar != nil { // Register instance to registry if err := a.opts.registrar.Register(a.opts.ctx, a.instance); err != nil return err } }// Listen for process exit signals c := make(chan os.Signal, 1) signal.Notify(c, a.opts.sigs...)
// Handle process exit and context exit g.Go(func() error { for { select { case <-ctx.Done(): return ctx.Err() case <-c: // Call kratos.App's stop method a.Stop() } } }) if err := g.Wait(); err != nil && !errors.Is(err, context.Canceled) { return err } return nil}4. Application Exit
Section titled “4. Application Exit”When starting, the Kratos instance listens for system process exit signals. Upon receiving an exit signal, kratos calls the Stop() method of the App struct.
func (a *App) Stop() error { // Check if registry configuration exists if a.opts.registrar != nil { // Deregister instance from registry if err := a.opts.registrar.Deregister(a.opts.ctx, a.instance); err != nil { return err } } // Control goroutine exit. When a.cancel() is called, the <-ctx.Done() listener in Run() method receives the message, unblocks, and then calls the server's Stop() method to stop the service. if a.cancel != nil { a.cancel() } return nil}