//go:build wireinject package main // Это файл, который пишет разработчик. Wire анализирует его и генерирует wire_gen.go. // Build-тег wireinject означает что этот файл НЕ попадает в итоговую сборку — // вместо него компилируется wire_gen.go. import ( "github.com/google/wire" "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" ) // InitializeHandler — инжектор. Wire смотрит на типы и строит граф зависимостей. // Разработчик перечисляет провайдеры, Wire разбирается кому что нужно. func InitializeHandler() (api.Handler, error) { wire.Build( // Конфиг config.AppConfig, // Инфраструктура — провайдеры-обёртки, потому что конструкторы // принимают строки (DSN, addr), а Wire различает по типу. provideDB, provideCache, events.NewEventBus, // Репозитории repository.NewUserRepo, repository.NewSessionRepo, repository.NewNotificationRepo, // Сервисы service.NewAuthService, service.NewUserService, service.NewNotificationService, // API api.NewHandler, // А теперь самое «весёлое» — wire.Bind. // Каждый конструктор принимает интерфейс, а Wire работает с типами. // Нужно ВРУЧНУЮ указать: какой тип реализует какой интерфейс. // // database.DB реализует 3 интерфейса в разных пакетах: wire.Bind(new(repository.UserDB), new(database.DB)), wire.Bind(new(repository.SessionDB), new(database.DB)), wire.Bind(new(repository.NotificationDB), new(database.DB)), // cache.Cache реализует 2 интерфейса: wire.Bind(new(repository.SessionCache), new(cache.Cache)), wire.Bind(new(service.AuthCache), new(cache.Cache)), // events.EventBus реализует 3 интерфейса: wire.Bind(new(service.AuthEventPublisher), new(events.EventBus)), wire.Bind(new(service.UserEventPublisher), new(events.EventBus)), wire.Bind(new(service.NotificationEventSubscriber), new(events.EventBus)), // repository.UserRepo реализует 2 интерфейса: wire.Bind(new(service.AuthUserRepository), new(repository.UserRepo)), wire.Bind(new(service.UserRepository), new(repository.UserRepo)), // Остальные репозитории — по одному интерфейсу: wire.Bind(new(service.AuthSessionRepository), new(repository.SessionRepo)), wire.Bind(new(service.NotificationRepository), new(repository.NotificationRepo)), // Сервисы тоже реализуют интерфейсы выше по графу: wire.Bind(new(service.UserAuthService), new(service.AuthService)), wire.Bind(new(service.NotificationUserService), new(service.UserService)), wire.Bind(new(api.UserService), new(service.UserService)), wire.Bind(new(api.AuthService), new(service.AuthService)), wire.Bind(new(api.NotificationService), new(service.NotificationService)), ) // 10 провайдеров + 18 wire.Bind = 28 строк конфигурации. // Для 12 зависимостей. И при каждом новом интерфейсе — новый Bind. // // А ТЕПЕРЬ ГЛАВНОЕ. // Откройте wire_gen.go и посмотрите что Wire нагенерировал. // Это та же самая каша что в cmd/antipattern/main.go: // // db, err := provideDB(configConfig) // userRepo := repository.NewUserRepo(db) // sessionRepo := repository.NewSessionRepo(db, cache) // authService := service.NewAuthService(userRepo, sessionRepo, cache, eventBus) // ... // // Последовательная инициализация. Жёсткий порядок. Eager — всё создаётся сразу. // // Wire решил ровно одну задачу: тебе не надо самому вычислять порядок строк. // Он сделал топологическую сортировку за тебя. Всё. // // Но хрупкость никуда не делась: // - Добавил зависимость → правь wire.go + wire.Bind + go generate // - Циклическая зависимость → Wire упадёт при генерации (тот же тупик) // - Ленивой инициализации нет — всё создаётся при старте // // По сути Wire автоматизирует неправильное решение. // Вместо того чтобы убрать хрупкость — он генерирует хрупкий код за тебя. return nil, nil } func provideDB(cfg *config.Config) (database.DB, error) { return database.New(cfg.DSN) } func provideCache(cfg *config.Config) cache.Cache { return cache.New(cfg.RedisAddr) }