Organize backend into product executable and shared Go modules
This commit is contained in:
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
|
||||
}
|
||||
Reference in new issue
Block a user