Organize backend into product executable and shared Go modules

This commit is contained in:
Stephan D committed 2026-09-12 14:14:08 +02:00
1 parent 981e84a4b4
commit f0720ec66b
382 files changed
+1944 -6989

No files matched your search

@@ -0,0 +1,177 @@
package server
import (
"context"
"errors"
"net/http"
"os"
"time"
ac "git.sendico.io/tech/sxchnge/product/app/api"
sharedruntime "git.sendico.io/tech/sxchnge/product/app/runtime"
"git.sendico.io/tech/sxchnge/shared/database"
mduration "git.sendico.io/tech/sxchnge/shared/duration"
"git.sendico.io/tech/sxchnge/shared/errors"
"git.sendico.io/tech/sxchnge/shared/logging"
"git.sendico.io/tech/sxchnge/shared/service"
"github.com/go-chi/chi/v5"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
)
type httpServerConf struct {
ListenAddress string `yaml:"listen_address"`
ReadHeaderTimeout int `yaml:"read_header_timeout"`
ShutdownTimeout int `yaml:"shutdown_timeout"`
}
// Config represents the server configuration
type Config struct {
API ac.ConfigProvider
DB *db.Config `yaml:"database"`
HTTPServer *httpServerConf `yaml:"http_server"`
}
// Instance represents an instance of the server
type Imp struct {
lifecycleCtx context.Context
lifecycleCancel context.CancelFunc
logger mlogger.Logger
api mservice.MicroService
config *Config
db db.Factory
httpServer *http.Server
runtime ac.RuntimeConfig
debug bool
file string
}
// Shutdown stops the server
func (i *Imp) Shutdown() {
if i.lifecycleCancel != nil {
i.lifecycleCancel()
}
if i.httpServer == nil {
return
}
// Shutdown HTTP server
ctx, cancel := context.WithTimeout(context.Background(), mduration.Param2Duration(i.config.HTTPServer.ShutdownTimeout, time.Second))
i.logger.Info("Shutting HTTP server down...")
if err := i.httpServer.Shutdown(ctx); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
i.logger.Warn("Failed to shutdown HTTP server gracefully", zap.Error(err))
cancel()
os.Exit(1)
}
}
cancel()
}
func (i *Imp) Run() error {
err := i.httpServer.ListenAndServe()
if errors.Is(err, http.ErrServerClosed) {
err = nil
}
i.logger.Info("HTTP Server stopped")
if err := i.api.Finish(context.Background()); err != nil {
i.logger.Warn("Error when finishing service", zap.Error(err))
}
i.db.CloseConnection()
if err != nil {
return merrors.OpaqueWrap(merrors.ErrInternal, err, "HTTP server stopped unexpectedly")
}
return nil
}
// Start starts the server
func (i *Imp) Start() error {
if i.lifecycleCancel != nil {
defer i.lifecycleCancel()
}
i.logger.Info("Starting...", zap.String("config_file", i.file), zap.Bool("debug_mode", i.debug))
// Load configuration file
data, err := os.ReadFile(i.file)
if err != nil {
i.logger.Error("Could not load configuration", zap.Error(err), zap.String("config_file", i.file))
return err
}
if err = i.decodeConfig(data); err != nil {
i.logger.Error("Failed to parse configuration", zap.Error(err))
return err
}
if i.db, err = db.NewConnection(i.logger, i.config.DB); err != nil {
i.logger.Error("Could not open database connection", zap.Error(err))
return err
}
rootRouter := chi.NewRouter()
apiRouter := chi.NewRouter()
if i.api, err = sharedruntime.CreateAPI(i.lifecycleCtx, i.logger, i.config.API.SharedConfig(), i.config.API, i.db, rootRouter, apiRouter, i.debug, i.runtime); err != nil {
i.db.CloseConnection()
return err
}
rootRouter.Mount("/", apiRouter)
// Startup the HTTP Server in a way that we can gracefully shut it down again
i.httpServer = &http.Server{
Addr: i.config.HTTPServer.ListenAddress,
Handler: rootRouter,
ReadHeaderTimeout: mduration.Param2Duration(i.config.HTTPServer.ReadHeaderTimeout, time.Second),
}
return i.Run()
}
func (i *Imp) decodeConfig(data []byte) error {
var raw struct {
API yaml.Node `yaml:"api"`
DB *db.Config `yaml:"database"`
HTTPServer *httpServerConf `yaml:"http_server"`
}
if err := yaml.Unmarshal(data, &raw); err != nil {
return err
}
apiConfig := i.runtime.NewConfig()
if apiConfig == nil {
return merrors.Internal("runtime config factory returned nil api config")
}
if raw.API.Kind == 0 {
return merrors.InvalidArgument("api configuration is required", "api")
}
if err := raw.API.Decode(apiConfig); err != nil {
return err
}
if raw.DB == nil || raw.HTTPServer == nil || apiConfig.SharedConfig() == nil || apiConfig.SharedConfig().Mw == nil {
return merrors.InvalidArgument("database, http_server and api.middleware configuration are required")
}
if raw.HTTPServer.ListenAddress == "" || raw.HTTPServer.ReadHeaderTimeout <= 0 || raw.HTTPServer.ShutdownTimeout <= 0 {
return merrors.InvalidArgument("HTTP address and positive timeouts are required")
}
i.config = &Config{
API: apiConfig,
DB: raw.DB,
HTTPServer: raw.HTTPServer,
}
return nil
}
func Create(logger mlogger.Logger, file string, debug bool, runtime ac.RuntimeConfig) (*Imp, error) {
lifecycleCtx, lifecycleCancel := context.WithCancel(context.Background())
srv := &Imp{
lifecycleCtx: lifecycleCtx,
lifecycleCancel: lifecycleCancel,
logger: logger,
debug: debug,
file: file,
runtime: runtime,
}
return srv, nil
}