First, install the necessary dependencies and tools:
# 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
# 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"}
Through the above diagram👆, we can intuitively observe the application’s call chain. Simplified, the process is as shown below👇
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...}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...}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}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}