package main import ( "log/slog" "net/http" "os" "github.com/olezhek28/di-demo/internal/api" "github.com/olezhek28/di-demo/internal/cache" "github.com/olezhek28/di-demo/internal/config" "github.com/olezhek28/di-demo/internal/database" "github.com/olezhek28/di-demo/internal/events" "github.com/olezhek28/di-demo/internal/repository" "github.com/olezhek28/di-demo/internal/service" ) func main() { cfg := config.New() // === Инфраструктура === db, err := database.New(cfg.DSN) if err != nil { slog.Error("не удалось подключиться к БД", "err", err) os.Exit(1) } c := cache.New(cfg.RedisAddr) // ⚠️ если перенести ниже sessionRepo — nil pointer // === Репозитории === userRepo := repository.NewUserRepo(db) sessionRepo := repository.NewSessionRepo(db, c) // ⚠️ зависит от db И cache notificationRepo := repository.NewNotificationRepo(db) // ⚠️ зависит от db // === EventBus === // Пока EventBus — простой брокер без зависимостей, всё нормально. // Проблемы начнутся когда EventBus понадобится зависимость (см. комментарии ниже). eventBus := events.NewEventBus() // === Сервисы === // Тут начинается самое интересное — перекрёстные зависимости. // Порядок критичен: authService нужен для userService, // а userService нужен для notificationService. // Переставь любые два — и привет, nil pointer. authService := service.NewAuthService(userRepo, sessionRepo, c, eventBus) // ⚠️ зависит от repos, cache, eventBus userService := service.NewUserService(userRepo, authService, eventBus) // ⚠️ зависит от authService — порядок! notificationService := service.NewNotificationService( // ⚠️ зависит от userService — порядок! notificationRepo, userService, eventBus, ) // === Сервер === handler := api.NewHandler(userService, authService, notificationService) srv := &http.Server{ Addr: cfg.HTTPAddr, Handler: handler.Routes(), } // ===================================================================== // === НОВАЯ ЗАДАЧА: EventBus должен сам доставлять уведомления === // ===================================================================== // // Продакт говорит: EventBus должен не просто пересылать события, // а сам доставлять уведомления. Нужно чтобы EventBus вызывал // NotificationService напрямую. // // Сейчас EventBus — простой брокер: // // type EventBus interface { // Publish(event string) // Subscribe(event string, handler func()) // } // func NewEventBus() EventBus // // А нужно так: // // type EventBus interface { // Publish(event string) // Subscribe(event string, handler func()) // } // func NewEventBus(notifications NotificationSender) EventBus // // Казалось бы — добавили одно поле. Но теперь зависимости: // // EventBus → нужен NotificationService // NotificationService → нужен UserService // UserService → нужен AuthService // AuthService → нужен EventBus ← ЦИКЛ // // Куда воткнуть EventBus в этот main.go? // // Попытка 1: создать EventBus после notificationService // eventBus := events.NewEventBus(notificationService) // ...но authService и userService уже созданы с eventBus выше (строки 48-49). // Поздно — они уже инициализированы со старым eventBus. // // Попытка 2: создать EventBus до сервисов, передать nil // eventBus := events.NewEventBus(nil) // NotificationService ещё не существует // ...потом: eventBus.SetNotificationService(notificationService) // Костыль с мутабельным состоянием. И гонка: кто-то вызовет // EventBus до SetNotificationService — получит nil pointer. // // Попытка 3: переписать всю цепочку // Разорвать одну из зависимостей, переделать интерфейсы... // Пол дня работы. И страшно — вдруг сломаю то что уже работает. // // Вот она — хрупкость в действии. // Одна зависимость с одним полем — и вся инициализация встала колом. // ===================================================================== slog.Info("всё хорошо, сервер запускается") if err = srv.ListenAndServe(); err != nil { slog.Error("ошибка сервера", "err", err) } _ = db.Close() _ = c.Close() }