Files
graceful-shutdown/internal/app/di.go
T
2026-04-13 08:10:14 +03:00

172 lines
5.7 KiB
Go

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
}