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:
2025-11-05 18:11:11 +01:00
parent a38a08ca17
commit 30320304f6
77 changed files with 19409 additions and 30 deletions

View File

@@ -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(),
)
}