Skip to main content
The App struct is the central component in Kratos that manages the entire application lifecycle. It coordinates the startup and shutdown of all services, handles signal management, and integrates with service registries.

The App Struct

The App struct is defined in app.go:
app.go:29-36
type App struct {
    opts     options
    ctx      context.Context
    cancel   context.CancelFunc
    mu       sync.Mutex
    instance *registry.ServiceInstance
}

Key Fields

Contains all configuration options including servers, registrar, signals, and lifecycle hooks.
Context for managing application lifecycle and cancellation propagation.
The service instance registered with the service registry.
Mutex for thread-safe access to the instance field.

Creating an Application

Use the New() function to create an application with functional options:
app.go:38-60
func New(opts ...Option) *App {
    o := options{
        ctx:              context.Background(),
        sigs:             []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT},
        registrarTimeout: 10 * time.Second,
    }
    if id, err := uuid.NewUUID(); err == nil {
        o.id = id.String()
    }
    for _, opt := range opts {
        opt(&o)
    }
    if o.logger != nil {
        log.SetLogger(o.logger)
    }
    ctx, cancel := context.WithCancel(o.ctx)
    return &App{
        ctx:    ctx,
        cancel: cancel,
        opts:   o,
    }
}

Configuration Options

Kratos uses the functional options pattern for configuration:
app := kratos.New(
    kratos.ID("service-001"),
    kratos.Name("user-service"),
    kratos.Version("v1.0.0"),
    kratos.Metadata(map[string]string{
        "region": "us-west",
    }),
)

Available Options

Here are all the configuration options available:
OptionDescriptionDefault
ID(id)Service instance IDAuto-generated UUID
Name(name)Service nameEmpty string
Version(version)Service versionEmpty string
Metadata(md)Service metadatanil
Server(srv...)Transport serversEmpty slice
Registrar(r)Service registrarnil
RegistrarTimeout(t)Registry timeout10 seconds
StopTimeout(t)Graceful stop timeout0 (no timeout)
Signal(sigs...)OS signals to handleSIGTERM, SIGQUIT, SIGINT
Logger(logger)Logger instanceDefault logger
Context(ctx)Root contextBackground context

Application Lifecycle

The Run Method

The Run() method orchestrates the entire application lifecycle:
app.go:82-151
func (a *App) Run() error {
    instance, err := a.buildInstance()
    if err != nil {
        return err
    }
    a.mu.Lock()
    a.instance = instance
    a.mu.Unlock()
    
    sctx := NewContext(a.ctx, a)
    eg, ctx := errgroup.WithContext(sctx)
    wg := sync.WaitGroup{}

    // Execute beforeStart hooks
    for _, fn := range a.opts.beforeStart {
        if err = fn(sctx); err != nil {
            return err
        }
    }
    
    // Start all servers concurrently
    octx := NewContext(a.opts.ctx, a)
    for _, srv := range a.opts.servers {
        server := srv
        // Goroutine for graceful stop
        eg.Go(func() error {
            <-ctx.Done()
            stopCtx := context.WithoutCancel(octx)
            if a.opts.stopTimeout > 0 {
                var cancel context.CancelFunc
                stopCtx, cancel = context.WithTimeout(stopCtx, a.opts.stopTimeout)
                defer cancel()
            }
            return server.Stop(stopCtx)
        })
        // Goroutine for server start
        wg.Add(1)
        eg.Go(func() error {
            wg.Done()
            return server.Start(octx)
        })
    }
    wg.Wait()
    
    // Register with service registry
    if a.opts.registrar != nil {
        rctx, rcancel := context.WithTimeout(ctx, a.opts.registrarTimeout)
        defer rcancel()
        if err = a.opts.registrar.Register(rctx, instance); err != nil {
            return err
        }
    }
    
    // Execute afterStart hooks
    for _, fn := range a.opts.afterStart {
        if err = fn(sctx); err != nil {
            return err
        }
    }

    // Wait for shutdown signal
    c := make(chan os.Signal, 1)
    signal.Notify(c, a.opts.sigs...)
    eg.Go(func() error {
        select {
        case <-ctx.Done():
            return nil
        case <-c:
            return a.Stop()
        }
    })
    
    if err = eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
        return err
    }
    
    // Execute afterStop hooks
    err = nil
    for _, fn := range a.opts.afterStop {
        err = fn(sctx)
    }
    return err
}

Lifecycle Stages

1

Build Instance

Creates a ServiceInstance with endpoints from all servers.
app.go:176-199
func (a *App) buildInstance() (*registry.ServiceInstance, error) {
    endpoints := make([]string, 0, len(a.opts.endpoints))
    for _, e := range a.opts.endpoints {
        endpoints = append(endpoints, e.String())
    }
    if len(endpoints) == 0 {
        for _, srv := range a.opts.servers {
            if r, ok := srv.(transport.Endpointer); ok {
                e, err := r.Endpoint()
                if err != nil {
                    return nil, err
                }
                endpoints = append(endpoints, e.String())
            }
        }
    }
    return &registry.ServiceInstance{
        ID:        a.opts.id,
        Name:      a.opts.name,
        Version:   a.opts.version,
        Metadata:  a.opts.metadata,
        Endpoints: endpoints,
    }, nil
}
2

Execute BeforeStart Hooks

Runs all registered beforeStart hooks sequentially. If any hook returns an error, the application stops.
3

Start Servers

Launches all transport servers concurrently using errgroup. Each server runs in its own goroutine.
4

Register Service

Registers the service instance with the configured service registry (if provided).
5

Execute AfterStart Hooks

Runs all registered afterStart hooks sequentially after successful startup.
6

Wait for Signal

Blocks until receiving an OS signal (SIGTERM, SIGINT, SIGQUIT by default) or context cancellation.
7

Graceful Shutdown

Stops all servers with optional timeout and deregisters from service registry.
8

Execute AfterStop Hooks

Runs all registered afterStop hooks for final cleanup.

The Stop Method

Graceful shutdown is handled by the Stop() method:
app.go:153-174
func (a *App) Stop() (err error) {
    sctx := NewContext(a.ctx, a)
    // Execute beforeStop hooks
    for _, fn := range a.opts.beforeStop {
        err = fn(sctx)
    }

    a.mu.Lock()
    instance := a.instance
    a.mu.Unlock()
    
    // Deregister from service registry
    if a.opts.registrar != nil && instance != nil {
        ctx, cancel := context.WithTimeout(NewContext(a.ctx, a), a.opts.registrarTimeout)
        defer cancel()
        if err = a.opts.registrar.Deregister(ctx, instance); err != nil {
            return err
        }
    }
    
    // Cancel context to trigger server shutdown
    if a.cancel != nil {
        a.cancel()
    }
    return err
}
The Stop() method triggers context cancellation, which signals all servers to shut down. Each server should implement graceful shutdown in its Stop() method.

Concurrency Model

Kratos uses Go’s concurrency primitives effectively:

Error Groups

Servers run in an errgroup.Group which provides:
  • Concurrent execution
  • Automatic error propagation
  • Context cancellation on first error
app.go:92
eg, ctx := errgroup.WithContext(sctx)

Wait Groups

Ensures all servers have started before proceeding to registration:
app.go:113-119
wg.Add(1)
eg.Go(func() error {
    wg.Done() // Signal that server start has begun
    return server.Start(octx)
})
wg.Wait() // Wait for all servers to begin starting

Context Management

The App provides context utilities for accessing application information:
app.go:204-206
// NewContext returns a new Context that carries app info
func NewContext(ctx context.Context, s AppInfo) context.Context {
    return context.WithValue(ctx, appKey{}, s)
}
app.go:208-212
// FromContext returns the AppInfo value stored in ctx
func FromContext(ctx context.Context) (s AppInfo, ok bool) {
    s, ok = ctx.Value(appKey{}).(AppInfo)
    return
}

AppInfo Interface

app.go:20-27
type AppInfo interface {
    ID() string
    Name() string
    Version() string
    Metadata() map[string]string
    Endpoint() []string
}

Complete Example

Complete Application Setup
package main

import (
    "context"
    "log"
    
    "github.com/go-kratos/kratos/v2"
    "github.com/go-kratos/kratos/v2/transport/http"
    "github.com/go-kratos/kratos/v2/transport/grpc"
)

func main() {
    httpSrv := http.NewServer(
        http.Address(":8000"),
    )
    grpcSrv := grpc.NewServer(
        grpc.Address(":9000"),
    )
    
    app := kratos.New(
        kratos.Name("helloworld"),
        kratos.Version("v1.0.0"),
        kratos.Server(
            httpSrv,
            grpcSrv,
        ),
        kratos.BeforeStart(func(ctx context.Context) error {
            log.Println("Application starting...")
            return nil
        }),
        kratos.AfterStart(func(ctx context.Context) error {
            log.Println("Application started successfully")
            return nil
        }),
        kratos.BeforeStop(func(ctx context.Context) error {
            log.Println("Application stopping...")
            return nil
        }),
        kratos.AfterStop(func(ctx context.Context) error {
            log.Println("Application stopped")
            return nil
        }),
    )
    
    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}

Best Practices

Use BeforeStart and AfterStop hooks for resource initialization and cleanup instead of doing it in main.
Configure StopTimeout to ensure graceful shutdown completes before forced termination.
Lifecycle hooks can return errors. Ensure proper error handling to prevent startup with incomplete initialization.
Store version, environment, and other relevant information in service metadata for debugging and routing.

Next Steps

Architecture Overview

Understand the overall framework architecture

Dependency Injection

Learn how to wire components together

Build docs developers (and LLMs) love