feat(epic1): implement core infrastructure (stories 1.1-1.5)
Implemented Epic 1 core kernel and infrastructure stories: Story 1.1: Enhanced DI Container - Added providers for database, health, metrics, and error bus - Extended CoreModule to include all core services Story 1.2: Database Layer with Ent ORM - Created Ent schema for User, Role, Permission, AuditLog entities - Implemented many-to-many relationships (User-Role, Role-Permission) - Created database client wrapper with connection pooling - Added database provider to DI container with migration support Story 1.3: Health Monitoring and Metrics System - Implemented health check registry and interface - Added database health checker - Created Prometheus metrics system with HTTP instrumentation - Added health and metrics providers to DI container Story 1.4: Error Handling and Error Bus - Implemented channel-based error bus - Created ErrorPublisher interface - Added error bus provider with lifecycle management Story 1.5: HTTP Server Foundation - Created HTTP server with Gin framework - Implemented comprehensive middleware stack: - Request ID generation - Structured logging - Panic recovery with error bus integration - Prometheus metrics collection - CORS support - Registered core routes: /healthz, /ready, /metrics - Integrated with FX lifecycle for graceful shutdown All components are integrated via DI container and ready for use.
This commit is contained in:
@@ -3,11 +3,19 @@ package di
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
configimpl "git.dcentral.systems/toolz/goplt/internal/config"
|
||||
errorbusimpl "git.dcentral.systems/toolz/goplt/internal/errorbus"
|
||||
"git.dcentral.systems/toolz/goplt/internal/health"
|
||||
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
||||
loggerimpl "git.dcentral.systems/toolz/goplt/internal/logger"
|
||||
"git.dcentral.systems/toolz/goplt/internal/metrics"
|
||||
"git.dcentral.systems/toolz/goplt/internal/server"
|
||||
"git.dcentral.systems/toolz/goplt/pkg/config"
|
||||
"git.dcentral.systems/toolz/goplt/pkg/errorbus"
|
||||
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
@@ -55,12 +63,147 @@ func ProvideLogger() fx.Option {
|
||||
})
|
||||
}
|
||||
|
||||
// ProvideDatabase creates an FX option that provides the database client.
|
||||
func ProvideDatabase() fx.Option {
|
||||
return fx.Provide(func(cfg config.ConfigProvider, lc fx.Lifecycle) (*database.Client, error) {
|
||||
dsn := cfg.GetString("database.dsn")
|
||||
if dsn == "" {
|
||||
return nil, fmt.Errorf("database DSN is not configured")
|
||||
}
|
||||
|
||||
maxConns := cfg.GetInt("database.max_connections")
|
||||
if maxConns == 0 {
|
||||
maxConns = 25
|
||||
}
|
||||
|
||||
maxIdleConns := cfg.GetInt("database.max_idle_connections")
|
||||
if maxIdleConns == 0 {
|
||||
maxIdleConns = 5
|
||||
}
|
||||
|
||||
connMaxLifetime := cfg.GetDuration("database.conn_max_lifetime")
|
||||
if connMaxLifetime == 0 {
|
||||
connMaxLifetime = 5 * time.Minute
|
||||
}
|
||||
|
||||
connMaxIdleTime := cfg.GetDuration("database.conn_max_idle_time")
|
||||
if connMaxIdleTime == 0 {
|
||||
connMaxIdleTime = 10 * time.Minute
|
||||
}
|
||||
|
||||
dbClient, err := database.NewClient(database.Config{
|
||||
DSN: dsn,
|
||||
MaxConnections: maxConns,
|
||||
MaxIdleConns: maxIdleConns,
|
||||
ConnMaxLifetime: connMaxLifetime,
|
||||
ConnMaxIdleTime: connMaxIdleTime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create database client: %w", err)
|
||||
}
|
||||
|
||||
// Register lifecycle hooks
|
||||
lc.Append(fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
// Run migrations on startup
|
||||
if err := dbClient.Migrate(ctx); err != nil {
|
||||
return fmt.Errorf("failed to run database migrations: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return dbClient.Close()
|
||||
},
|
||||
})
|
||||
|
||||
return dbClient, nil
|
||||
})
|
||||
}
|
||||
|
||||
// ProvideErrorBus creates an FX option that provides the error bus.
|
||||
func ProvideErrorBus() fx.Option {
|
||||
return fx.Provide(func(log logger.Logger, lc fx.Lifecycle) (errorbus.ErrorPublisher, error) {
|
||||
bufferSize := 100 // Can be made configurable
|
||||
bus := errorbusimpl.NewChannelBus(log, bufferSize)
|
||||
|
||||
// Register lifecycle hook to close the bus on shutdown
|
||||
lc.Append(fx.Hook{
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return bus.Close()
|
||||
},
|
||||
})
|
||||
|
||||
return bus, nil
|
||||
})
|
||||
}
|
||||
|
||||
// ProvideHealthRegistry creates an FX option that provides the health check registry.
|
||||
func ProvideHealthRegistry() fx.Option {
|
||||
return fx.Provide(func(dbClient *database.Client) (*health.Registry, error) {
|
||||
registry := health.NewRegistry()
|
||||
|
||||
// Register database health checker
|
||||
registry.Register("database", health.NewDatabaseChecker(dbClient))
|
||||
|
||||
return registry, nil
|
||||
})
|
||||
}
|
||||
|
||||
// ProvideMetrics creates an FX option that provides the Prometheus metrics registry.
|
||||
func ProvideMetrics() fx.Option {
|
||||
return fx.Provide(func() *metrics.Metrics {
|
||||
return metrics.NewMetrics()
|
||||
})
|
||||
}
|
||||
|
||||
// ProvideHTTPServer creates an FX option that provides the HTTP server.
|
||||
func ProvideHTTPServer() fx.Option {
|
||||
return fx.Provide(func(
|
||||
cfg config.ConfigProvider,
|
||||
log logger.Logger,
|
||||
healthRegistry *health.Registry,
|
||||
metricsRegistry *metrics.Metrics,
|
||||
errorBus errorbus.ErrorPublisher,
|
||||
lc fx.Lifecycle,
|
||||
) (*server.Server, error) {
|
||||
srv, err := server.NewServer(cfg, log, healthRegistry, metricsRegistry, errorBus)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create HTTP server: %w", err)
|
||||
}
|
||||
|
||||
// Register lifecycle hooks
|
||||
lc.Append(fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
// Start server in a goroutine
|
||||
go func() {
|
||||
if err := srv.Start(); err != nil && err != http.ErrServerClosed {
|
||||
log.Error("HTTP server error",
|
||||
logger.String("error", err.Error()),
|
||||
)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return srv.Shutdown(ctx)
|
||||
},
|
||||
})
|
||||
|
||||
return srv, nil
|
||||
})
|
||||
}
|
||||
|
||||
// CoreModule returns an FX option that provides all core services.
|
||||
// This includes configuration and logging.
|
||||
// This includes configuration, logging, database, error bus, health checks, metrics, and HTTP server.
|
||||
func CoreModule() fx.Option {
|
||||
return fx.Options(
|
||||
ProvideConfig(),
|
||||
ProvideLogger(),
|
||||
ProvideDatabase(),
|
||||
ProvideErrorBus(),
|
||||
ProvideHealthRegistry(),
|
||||
ProvideMetrics(),
|
||||
ProvideHTTPServer(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user