modelRT/app/providers.go

218 lines
7.2 KiB
Go
Raw Normal View History

package app
import (
"context"
"fmt"
"log"
"net/http"
"os"
"time"
"modelRT/config"
"modelRT/constants"
"modelRT/database"
"modelRT/diagram"
locker "modelRT/distributedlock"
"modelRT/logger"
"modelRT/middleware"
"modelRT/model"
"modelRT/mq"
"modelRT/pool"
"modelRT/real-time-data/alert"
"modelRT/router"
"modelRT/task"
"modelRT/util"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/gomodule/redigo/redis"
"github.com/panjf2000/ants/v2"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"gorm.io/gorm"
)
// provideConfig loads the service configuration, then initializes the global
// logger. Because every other provider depends transitively on the config,
// initializing the logger here guarantees it is ready before any provider that
// logs. The returned cleanup flushes the logger on shutdown.
func provideConfig(cli CLIArgs) (config.ModelRTConfig, func(), error) {
cfg := config.ReadAndInitConfig(cli.ConfigDir, cli.ConfigName, cli.ConfigType)
logger.InitLoggerInstance(cfg.LoggerConfig)
cleanup := func() { _ = logger.GetLoggerInstance().Sync() }
return cfg, cleanup, nil
}
// provideTracerProvider initializes the OTel tracer provider. A failure here is
// non-fatal: tracing is simply disabled, matching the original startup behavior.
func provideTracerProvider(ctx context.Context, cfg config.ModelRTConfig) (*sdktrace.TracerProvider, func()) {
tp, err := middleware.InitTracerProvider(ctx, cfg)
if err != nil {
log.Printf("warn: OTLP tracer init failed, tracing disabled: %v", err)
return nil, func() {}
}
cleanup := func() {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = tp.Shutdown(shutdownCtx)
}
return tp, cleanup
}
// provideServiceToken generates the signed client token bound to this host.
func provideServiceToken(cfg config.ModelRTConfig) (ServiceToken, error) {
hostName, err := os.Hostname()
if err != nil {
return "", fmt.Errorf("get host name failed: %w", err)
}
tok, err := util.GenerateClientToken(hostName, cfg.ServiceName, cfg.SecretKey)
if err != nil {
return "", fmt.Errorf("generate client token failed: %w", err)
}
return ServiceToken(tok), nil
}
// providePostgres opens the global Postgres client and closes the underlying
// sql.DB on shutdown.
func providePostgres(ctx context.Context, cfg config.ModelRTConfig) (*gorm.DB, func(), error) {
db := database.InitPostgresDBInstance(ctx, cfg.PostgresDBURI)
cleanup := func() {
if sqlDB, err := db.DB(); err == nil {
_ = sqlDB.Close()
}
}
return db, cleanup, nil
}
// provideParsePool builds the ants pool that runs model parse tasks.
func provideParsePool(cfg config.ModelRTConfig) (ParsePool, func(), error) {
p, err := ants.NewPoolWithFunc(cfg.ParseConcurrentQuantity, pool.ParseFunc)
if err != nil {
return nil, nil, fmt.Errorf("init concurrent parse task pool failed: %w", err)
}
return ParsePool(p), func() { p.Release() }, nil
}
// provideSearchPool builds the redigo pool used for autocomplete/search and
// wires it into the global autocompleter.
func provideSearchPool(cfg config.ModelRTConfig) (*redis.Pool, func(), error) {
sp, err := util.NewRedigoPool(cfg.StorageRedisConfig)
if err != nil {
return nil, nil, fmt.Errorf("init redigo pool failed: %w", err)
}
model.InitAutocompleterWithPool(sp)
return sp, func() { _ = sp.Close() }, nil
}
// provideStorageRedis opens the go-redis client backing diagram/storage data.
func provideStorageRedis(cfg config.ModelRTConfig) (StorageRedis, func()) {
c := diagram.InitRedisClientInstance(cfg.StorageRedisConfig, cfg.DeployEnv)
return StorageRedis(c), func() { _ = c.Close() }
}
// provideLockerRedis opens the go-redis client backing the distributed lock.
func provideLockerRedis(cfg config.ModelRTConfig) (LockerRedis, func()) {
c := locker.InitClientInstance(cfg.LockerRedisConfig, cfg.DeployEnv)
return LockerRedis(c), func() { _ = c.Close() }
}
// provideAnchorPool builds the ants pool that runs real-time anchor param tasks.
func provideAnchorPool(cfg config.ModelRTConfig) (AnchorPool, func(), error) {
p, err := pool.AnchorPoolInit(cfg.RTDReceiveConcurrentQuantity)
if err != nil {
return nil, nil, fmt.Errorf("init concurrent anchor param task pool failed: %w", err)
}
return AnchorPool(p), func() { p.Release() }, nil
}
// provideRabbit opens the global RabbitMQ proxy.
func provideRabbit(ctx context.Context, cfg config.ModelRTConfig) (*mq.RabbitMQProxy, func()) {
proxy := mq.InitRabbitProxy(ctx, cfg.RabbitMQConfig)
return proxy, func() { mq.CloseRabbitProxy() }
}
// provideTaskWorker initializes the async task worker. A failure is non-fatal:
// the service continues without a worker, matching the original startup behavior.
// The returned worker may be nil; App.StartBackground guards against that.
func provideTaskWorker(ctx context.Context, cfg config.ModelRTConfig, db *gorm.DB) (*task.TaskWorker, func()) {
tw, err := task.InitTaskWorker(ctx, cfg, db)
if err != nil {
logger.Error(ctx, "failed to initialize task worker", "error", err)
return nil, func() {}
}
return tw, func() { _ = tw.Stop() }
}
// provideAlertManager initializes the global alert event manager. Its value is
// held by App purely so Wire keeps it in the dependency graph.
func provideAlertManager() *alert.EventManager {
return alert.InitAlertEventManager()
}
// provideEngine builds the gin engine with CORS, routes and (outside production)
// the swagger UI.
func provideEngine(cfg config.ModelRTConfig, token ServiceToken) *gin.Engine {
if cfg.DeployEnv == constants.ProductionDeployMode {
gin.SetMode(gin.ReleaseMode)
}
engine := gin.New()
engine.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
router.RegisterRoutes(engine, string(token))
if cfg.DeployEnv != constants.ProductionDeployMode {
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
return engine
}
// provideServer builds the HTTP server bound to the configured address.
func provideServer(cfg config.ModelRTConfig, engine *gin.Engine) *http.Server {
return &http.Server{
Addr: cfg.ServiceAddr,
Handler: engine,
}
}
// provideApp aggregates every constructed resource into the App. App holds the
// resource handles (pools, redis clients, etc.) so that Wire keeps their
// providers — and therefore their cleanups — in the dependency graph.
func provideApp(
cfg config.ModelRTConfig,
server *http.Server,
db *gorm.DB,
tp *sdktrace.TracerProvider,
parsePool ParsePool,
searchPool *redis.Pool,
storage StorageRedis,
lockerClient LockerRedis,
anchorPool AnchorPool,
rabbit *mq.RabbitMQProxy,
taskWorker *task.TaskWorker,
alertMgr *alert.EventManager,
) *App {
return &App{
cfg: cfg,
server: server,
db: db,
tp: tp,
parsePool: parsePool,
searchPool: searchPool,
storage: storage,
locker: lockerClient,
anchorPool: anchorPool,
rabbit: rabbit,
taskWorker: taskWorker,
alertMgr: alertMgr,
}
}