func (k *Kernel) Shutdown(ctx context.Context) error {
	if k.state != StateRunning {
		k.Log.WithPrefix("app").Warn("Application cannot be shutdown since current state is not 'running'")
		return nil
	}
	k.state = StateStopping
	defer func() {
		k.state = StateStopped
	}()
	if k.server != nil {
		if err := k.server.Shutdown(ctx); err != nil {
			k.Log.Errorf("server shutdown error: %v\n", err)
		} else {
			k.Log.Infoln("HTTP server stopped")
		}
	}
	k.context.cancel()
	done := make(chan struct{})
	go func() {
		k.wg.Wait()
		close(done)
	}()
	for _, fn := range k.shutdownFns {
		shutdownErr := fn()
		if shutdownErr != nil {
			k.Log.Errorf("shutdown function returned error: %v\n", shutdownErr)
		}
	}
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-done:
	}
	return k.DB.Close()
}
Shutdown stops the application.
package app
import (
	"context"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"sync"
	"time"
	"go-webapp-example/internal/pkg"
	"go-webapp-example/internal/pkg/audit"
	"go-webapp-example/internal/pkg/permission"
	"go-webapp-example/internal/pkg/quote"
	"go-webapp-example/internal/pkg/role"
	"go-webapp-example/internal/pkg/user"
	"go-webapp-example/pkg/auth"
	"go-webapp-example/pkg/cache"
	"go-webapp-example/pkg/db"
	"go-webapp-example/pkg/i18n"
	"go-webapp-example/pkg/log"
	"go-webapp-example/pkg/router"
	"go-webapp-example/pkg/session"
	"github.com/pkg/errors"
)
const (
	StateStopped int = iota
	StateStarting
	StateRunning
	StateStopping
)
var version = "develop"
type Kernel struct {
	server *http.Server
	Log log.Logger
	Config *Config
	DB *db.Connection
	Session *session.Store
	Cache *cache.Store
	Auth *auth.Manager
	Router *router.Mux
	Locale *i18n.Locale
	state int
	services *pkg.Services
	wg *sync.WaitGroup
	context cancelContext
	requestLog log.Logger
	shutdownFns []func() error
}
type cancelContext struct {
	cancel context.CancelFunc
	ctx    context.Context
}
func New() (*Kernel, error) {
	config := LoadConfig()
	logger := log.New(os.Stderr, config.Log.Level, config.Log.Dir)
	database, err := db.New("mysql", config.Database.DSN(), logger.WithPrefix("db"))
	if err != nil {
		return nil, errors.Wrap(err, "failed to connect to db")
	}
	authManager, err := auth.New(database, logger.WithPrefix("auth"))
	if err != nil {
		return nil, errors.Wrap(err, "failed to create auth manager")
	}
	sess := session.New(database.Connection())
	locale, err := i18n.FromFiles(config.Server.LocalesDir, config.App.Locale)
	if err != nil {
		logger.Errorf("failed to load locales: %s", err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	app := &Kernel{
		state:   StateStarting,
		Log:     logger,
		Config:  config,
		DB:      database,
		Session: sess,
		Cache:   cache.New(),
		Auth:    authManager,
		Router:  router.New(),
		Locale:  locale,
		wg:      &sync.WaitGroup{},
		context: cancelContext{cancel: cancel, ctx: ctx},
		services: &pkg.Services{},
	}
	err = app.setupLogger()
	if err != nil {
		return nil, errors.WithStack(err)
	}
	app.setupServices()
	app.setupAuth()
	app.setupRouter()
	app.setupBackendRoutes()
	app.setupFrontendRoutes()
	app.setupGraphQL()
	app.state = StateRunning
	return app, nil
}
func (k *Kernel) setupLogger() error {
	requestW, requestCleanup, err := log.GetMaxLatencyWriter(filepath.Join(k.Config.Log.Dir, "access.log"))
	if err != nil {
		return errors.WithStack(err)
	}
	k.registerShutdownFn(func() error {
		requestCleanup()
		return nil
	})
	k.requestLog = log.NewFromWriter(requestW)
	return nil
}
func (k *Kernel) setupServices() {
	k.services.DB = k.DB
	k.services.Audit = audit.NewService(audit.NewStore(k.DB), k.Log.WithPrefix("audit"))
	k.services.Quote = quote.NewService(quote.NewStore(k.DB, k.services.Audit))
	k.services.User = user.NewService(user.NewStore(k.DB, k.Auth, k.services.Audit), k.Session)
	k.services.Role = role.NewService(role.NewStore(k.DB, k.Auth))
	k.services.Permission = permission.NewService(permission.NewStore(k.DB, k.Auth))
}
func (k *Kernel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	k.Router.ServeHTTP(w, r)
}
func (k *Kernel) Listen(server *http.Server) {
	var err error
	k.server = server
	logger := k.Log.WithPrefix("http.server")
	logger.WithFields(log.Fields{"port": server.Addr}).Infof("staring http server")
	listen := func() error {
		if err = server.ListenAndServe(); err != http.ErrServerClosed {
			logger.Error(fmt.Sprintf("failed to start http server: %v", err))
			return err
		}
		return nil
	}
	for {
		if k.state == StateStopping || k.state == StateStopped {
			logger.Tracef("skipping restarts of server because app is not in running state: state is %d", k.state)
			return
		}
		if err = listen(); err != nil {
			time.Sleep(2 * time.Second)
			logger.Infof("re-staring http server after error on: %v", server.Addr)
			continue
		}
		return
	}
}