Make product API versions independently installable

This commit is contained in:
Stephan D committed 2026-09-13 10:26:06 +02:00
1 parent ce6583f454
commit d04c8b12ab
69 files changed
+755 -404

No files matched your search

+54 -77
View File
@@ -6,9 +6,12 @@ import (
"git.sendico.io/tech/sxchnge/product/app/api"
amr "git.sendico.io/tech/sxchnge/product/app/health"
"git.sendico.io/tech/sxchnge/product/app/health/status"
httptransport "git.sendico.io/tech/sxchnge/product/app/http"
"git.sendico.io/tech/sxchnge/product/app/urls"
"git.sendico.io/tech/sxchnge/product/sessions/browser"
"git.sendico.io/tech/sxchnge/shared/database"
"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"
@@ -16,93 +19,67 @@ import (
)
func CreateAPI(
lifecycleCtx context.Context,
ctx context.Context,
logger mlogger.Logger,
config *api.Config,
profileConfig api.ConfigProvider,
db db.Factory,
rootRouter *chi.Mux,
apiRouter *chi.Mux,
rootRouter, apiRouter *chi.Mux,
debug bool,
runtime api.RuntimeConfig,
) (mservice.MicroService, error) {
p := &APIImp{
lifecycleCtx: lifecycleCtx,
logger: logger.Named("api"),
config: config,
profile: profileConfig,
db: db,
runtime: runtime,
) (_ mservice.MicroService, result error) {
if config == nil || config.Mw == nil {
return nil, merrors.InvalidArgument("API middleware configuration is required")
}
if err := p.installRuntimeBasics(logger, db, rootRouter, apiRouter, debug); err != nil {
endpoint := os.Getenv(config.Mw.EndPointEnv)
mounts, err := resolveMounts(config.Versions, endpoint, runtime.Versions)
if err != nil {
return nil, err
}
if err := p.bootstrapConfiguredRuntime(config, db); err != nil {
return nil, err
if runtime.Bootstrap == nil {
return nil, merrors.InvalidArgument("runtime bootstrap is required")
}
p.logger.Info("Installing microservices...", zap.String("runtime", runtime.RuntimeName()))
if err := p.installServices(); err != nil {
p.logger.Error("Failed to install a microservice", zap.Error(err))
return nil, err
}
p.logger.Info("Microservices installation complete", zap.Int("microservices", len(p.services)))
return p, nil
}
func (a *APIImp) installRuntimeBasics(
logger mlogger.Logger,
db db.Factory,
rootRouter *chi.Mux,
apiRouter *chi.Mux,
debug bool,
) error {
var err error
if a.domain, err = domainprovider.CreateDomainProvider(
a.logger,
a.config.Mw.DomainEnv,
a.config.Mw.APIProtocolEnv,
a.config.Mw.EndPointEnv,
); err != nil {
a.logger.Error("Failed to initialize domain provider")
return err
}
a.logger.Info("Domain provider installed")
apiEndpoint := os.Getenv(a.config.Mw.EndPointEnv)
if a.health, err = amr.NewHealthRouter(a.logger, rootRouter, apiEndpoint); err != nil {
a.logger.Error("Failed to create healthcheck router", zap.Error(err), zap.String("api_endpoint", apiEndpoint))
return err
}
if a.mw, err = httptransport.CreateMiddleware(
logger,
db,
apiRouter,
a.config.Mw,
a.config.AuthStorage,
debug,
a.config.Notifications,
); err != nil {
a.logger.Error("Failed to create middleware", zap.Error(err))
return err
}
a.logger.Info("Middleware installed", zap.Bool("debug_mode", debug))
return nil
}
func (a *APIImp) bootstrapConfiguredRuntime(_ *api.Config, _ db.Factory) error {
if a.runtime.Bootstrap != nil {
components, err := a.runtime.Bootstrap(a)
// Resolve every URL before opening resources or publishing routes.
scopes := make([]api.Scope, len(mounts))
for i, mount := range mounts {
urls, err := domainprovider.CreateDomainProvider(logger, os.Getenv(config.Mw.DomainEnv), os.Getenv(config.Mw.APIProtocolEnv), mount.Path)
if err != nil {
a.logger.Error("Failed to bootstrap runtime profile", zap.Error(err), zap.String("runtime", a.runtime.RuntimeName()))
return err
return nil, err
}
a.components = components
a.logger.Info("Runtime profile bootstrap complete",
zap.String("runtime", a.runtime.RuntimeName()),
zap.Int("components", len(components)))
scopes[i] = api.Scope{Router: chi.NewRouter(), Mount: mount.Path, URLs: urls, Logger: logger.With(zap.String("api_version", mount.Version), zap.String("mount", mount.Path))}
scopes[i].Router.Use(browser.Scope(mount.Path))
}
return nil
a := &instance{logger: logger.Named("api").With(zap.String("runtime", runtime.RuntimeName()))}
defer func() {
if result != nil {
if err := a.Finish(context.Background()); err != nil {
a.logger.Warn("Failed startup resource cleanup", zap.Error(err))
}
}
}()
a.mw, err = httptransport.CreateMiddleware(logger, apiRouter, config.Mw, endpoint, debug)
if err != nil {
return nil, err
}
deps, cleanup, err := runtime.Bootstrap(ctx, a.logger, config, db)
a.cleanup = cleanup
if err != nil {
return nil, err
}
for i, mount := range mounts {
if err := runtime.Versions[mount.Version](scopes[i], deps); err != nil {
return nil, merrors.OpaqueWrap(merrors.ErrInternal, err, "API version installation failed: "+mount.Version)
}
}
// Attach only after every installer succeeds.
for i, mount := range mounts {
apiRouter.Mount(mount.Path, scopes[i].Router)
scopes[i].Logger.Info("API version installed")
}
a.health, err = amr.NewHealthRouter(a.logger, rootRouter, endpoint)
if err != nil {
return nil, err
}
a.logger.Info("API installation complete", zap.Int("versions", len(mounts)))
a.health.SetStatus(health.SSRunning)
return a, nil
}