package app import ( "context" "log/slog" "os" "github.com/olezhek28/graceful-shutdown-demo/internal/api" "github.com/olezhek28/graceful-shutdown-demo/internal/cache" "github.com/olezhek28/graceful-shutdown-demo/internal/closer" "github.com/olezhek28/graceful-shutdown-demo/internal/config" "github.com/olezhek28/graceful-shutdown-demo/internal/database" "github.com/olezhek28/graceful-shutdown-demo/internal/events" "github.com/olezhek28/graceful-shutdown-demo/internal/repository" "github.com/olezhek28/graceful-shutdown-demo/internal/service" ) // diContainer — контейнер зависимостей с ленивой инициализацией. // // Это тот же контейнер из видео про DI, но при создании каждого ресурса // с методом Close() мы сразу регистрируем его в глобальном closer. // closer.Add() вызывается прямо в геттере — одна строка, и ресурс // автоматически закроется при graceful shutdown в правильном LIFO-порядке. // // Почему порядок закрытия детерминирован при ленивой инициализации: // initDeps() вызывается в одной горутине. Ленивая инициализация — это // depth-first обход графа зависимостей. Порядок определяется графом: // // Handler() → UserService() → UserRepo() → DB() ← 1-й closer.Add // → AuthService() → SessionRepo() → Cache() ← 2-й closer.Add // // DB всегда создаётся раньше Cache (UserRepo запрашивает DB до того, // как SessionRepo запросит Cache). Граф не меняется → порядок не меняется. type diContainer struct { // Инфраструктура db database.DB cache cache.Cache eventBus events.EventBus // Репозитории userRepo repository.UserRepo sessionRepo repository.SessionRepo notificationRepo repository.NotificationRepo // Сервисы authService service.AuthService userService service.UserService notificationService service.NotificationService // API handler api.Handler } // newDIContainer создаёт новый пустой контейнер. func newDIContainer() *diContainer { return &diContainer{} } // DB возвращает подключение к базе данных. // При создании — сразу регистрирует Close() в глобальном closer. // БД создаётся одной из первых — значит закроется одной из последних (LIFO). func (d *diContainer) DB() database.DB { if d.db == nil { db, err := database.New(config.AppConfig().DSN) if err != nil { slog.Error("не удалось подключиться к БД", "err", err) os.Exit(1) } closer.Add("база данных", func(_ context.Context) error { return db.Close() }) d.db = db } return d.db } // EventBus возвращает шину событий. func (d *diContainer) EventBus() events.EventBus { if d.eventBus == nil { d.eventBus = events.NewEventBus() } return d.eventBus } // Cache возвращает подключение к кэшу. // При создании — сразу регистрирует Close() в глобальном closer. // Кэш создаётся после БД — значит закроется раньше БД (LIFO). func (d *diContainer) Cache() cache.Cache { if d.cache == nil { c := cache.New(config.AppConfig().RedisAddr) closer.Add("кэш", func(_ context.Context) error { return c.Close() }) d.cache = c } return d.cache } // UserRepo возвращает репозиторий пользователей. func (d *diContainer) UserRepo() repository.UserRepo { if d.userRepo == nil { d.userRepo = repository.NewUserRepo(d.DB()) } return d.userRepo } // SessionRepo возвращает репозиторий сессий. func (d *diContainer) SessionRepo() repository.SessionRepo { if d.sessionRepo == nil { d.sessionRepo = repository.NewSessionRepo(d.DB(), d.Cache()) } return d.sessionRepo } // NotificationRepo возвращает репозиторий уведомлений. func (d *diContainer) NotificationRepo() repository.NotificationRepo { if d.notificationRepo == nil { d.notificationRepo = repository.NewNotificationRepo(d.DB()) } return d.notificationRepo } // AuthService возвращает сервис авторизации. func (d *diContainer) AuthService() service.AuthService { if d.authService == nil { d.authService = service.NewAuthService(d.UserRepo(), d.SessionRepo(), d.Cache(), d.EventBus()) } return d.authService } // UserService возвращает сервис пользователей. func (d *diContainer) UserService() service.UserService { if d.userService == nil { d.userService = service.NewUserService(d.UserRepo(), d.AuthService(), d.EventBus()) } return d.userService } // NotificationService возвращает сервис уведомлений. func (d *diContainer) NotificationService() service.NotificationService { if d.notificationService == nil { d.notificationService = service.NewNotificationService(d.NotificationRepo(), d.UserService(), d.EventBus()) } return d.notificationService } // Handler возвращает HTTP-хендлер. func (d *diContainer) Handler() api.Handler { if d.handler == nil { d.handler = api.NewHandler( d.UserService(), d.AuthService(), d.NotificationService(), ) } return d.handler }