import
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
package main
|
||||
|
||||
// Это пример инициализации через Uber Fx — runtime DI-контейнер.
|
||||
// Fx делает всё в рантайме: через рефлексию анализирует сигнатуры конструкторов
|
||||
// и автоматически собирает граф зависимостей.
|
||||
//
|
||||
// Плюсы:
|
||||
// - Не нужна кодогенерация
|
||||
// - Lifecycle хуки из коробки
|
||||
//
|
||||
// Минусы:
|
||||
// - Рефлексия: ошибки только в рантайме, а не при компиляции
|
||||
// - fx.Annotate + fx.As для каждого интерфейса — бойлерплейта не меньше, чем wire.Bind
|
||||
// - Один конкретный тип → несколько интерфейсов? Нужны обёртки-адаптеры (fx.Out)
|
||||
// - Свой логгер (zap), свой lifecycle — навязывает архитектуру
|
||||
// - Магия: непонятно что происходит, пока не запустишь
|
||||
//
|
||||
// =====================================================
|
||||
// ПОЧЕМУ ЗДЕСЬ СТОЛЬКО БОЙЛЕРПЛЕЙТА?
|
||||
// =====================================================
|
||||
//
|
||||
// В этом проекте мы определяем интерфейсы по месту использования —
|
||||
// Go-идиоматичный подход (Interface Segregation Principle):
|
||||
//
|
||||
// // service/auth.go — свой интерфейс
|
||||
// type AuthUserRepository interface { ... }
|
||||
//
|
||||
// // service/user.go — свой интерфейс
|
||||
// type UserRepository interface { ... }
|
||||
//
|
||||
// Один repository.UserRepo реализует оба. Fx не умеет это разрулить
|
||||
// без fx.Out/fx.In структур и named-тегов — отсюда 300+ строк.
|
||||
//
|
||||
// Если у вас другой подход — общие интерфейсы в одном пакете:
|
||||
//
|
||||
// // shared/interfaces.go
|
||||
// type UserRepository interface { ... } // один на всех потребителей
|
||||
//
|
||||
// То Fx ложится ГОРАЗДО проще: один тип → один интерфейс → нет проблемы.
|
||||
// Просто fx.Provide(repository.NewUserRepo) — и готово.
|
||||
//
|
||||
// Это не плохой подход, просто другой. Java/Spring так и работают.
|
||||
// Fx по сути портированный Dagger/Spring — он заточен под shared interfaces.
|
||||
// Если у вас на проекте именно такая архитектура — Fx будет комфортным.
|
||||
// =====================================================
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/fx"
|
||||
|
||||
"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() {
|
||||
fx.New(
|
||||
// Конфиг
|
||||
fx.Provide(config.AppConfig),
|
||||
|
||||
// === Инфраструктура ===
|
||||
// Здесь начинается самое «весёлое».
|
||||
//
|
||||
// database.DB реализует 3 интерфейса:
|
||||
// - repository.UserDB
|
||||
// - repository.SessionDB
|
||||
// - repository.NotificationDB
|
||||
//
|
||||
// cache.Cache реализует 2 интерфейса:
|
||||
// - repository.SessionCache
|
||||
// - service.AuthCache
|
||||
//
|
||||
// events.EventBus реализует 3 интерфейса:
|
||||
// - service.AuthEventPublisher
|
||||
// - service.UserEventPublisher
|
||||
// - service.NotificationEventSubscriber
|
||||
//
|
||||
// В Fx нельзя просто написать fx.As(new(A), new(B), new(C)).
|
||||
// Если один провайдер возвращает конкретный тип, а его нужно
|
||||
// подать как РАЗНЫЕ интерфейсы в разные потребители — нужно
|
||||
// писать специальные структуры-адаптеры с fx.Out.
|
||||
//
|
||||
// Либо — провайдить конкретный тип + отдельные provide-обёртки
|
||||
// для каждого интерфейса. Оба варианта — бойлерплейт.
|
||||
//
|
||||
// Мы используем fx.Out структуры — каноничный подход Fx.
|
||||
|
||||
fx.Provide(provideDB), // → dbOut{UserDB, SessionDB, NotificationDB}
|
||||
fx.Provide(provideCache), // → cacheOut{SessionCache, AuthCache}
|
||||
fx.Provide(provideEvents), // → eventsOut{AuthPub, UserPub, NotifSub}
|
||||
|
||||
// === Репозитории ===
|
||||
// Та же история: repository.UserRepo реализует 2 интерфейса (service.AuthUserRepository
|
||||
// и service.UserRepository), поэтому нужна fx.Out обёртка.
|
||||
fx.Provide(provideUserRepo), // → userRepoOut{AuthUserRepo, UserRepo}
|
||||
fx.Provide(provideSessionRepo), // → fx.As(service.AuthSessionRepository)
|
||||
fx.Provide(provideNotificationRepo), // → fx.As(service.NotificationRepository)
|
||||
|
||||
// === Сервисы ===
|
||||
// И здесь то же самое.
|
||||
fx.Provide(provideAuthService), // → authServiceOut{UserAuth, APIAuth}
|
||||
fx.Provide(provideUserService), // → userServiceOut{NotifUser, APIUser}
|
||||
fx.Provide(provideNotificationService), // → api.NotificationService
|
||||
|
||||
// Handler — тоже нужна обёртка, потому что зависимости named.
|
||||
fx.Provide(provideHandler),
|
||||
|
||||
// HTTP-сервер — через fx.Invoke.
|
||||
fx.Invoke(startHTTPServer),
|
||||
|
||||
// Итого: чтобы «автоматически» собрать граф из 12 зависимостей,
|
||||
// мы написали 7 fx.Out структур + 7 fx.In структур + 11 provide-обёрток.
|
||||
// Файл ~300 строк для того же результата что 40 строк в DI-контейнере.
|
||||
//
|
||||
// А ТЕПЕРЬ ГЛАВНОЕ — как Fx работает внутри?
|
||||
//
|
||||
// При вызове fx.New() он:
|
||||
// 1. Собирает все провайдеры
|
||||
// 2. Через рефлексию анализирует сигнатуры (кто что принимает, кто что возвращает)
|
||||
// 3. Строит граф зависимостей
|
||||
// 4. Делает топологическую сортировку
|
||||
// 5. Вызывает ВСЕ конструкторы разом, в правильном порядке
|
||||
//
|
||||
// То есть инициализация — EAGER, не ленивая.
|
||||
// Всё создаётся при старте, как в антипаттерне и в Wire.
|
||||
// Просто порядок вычисляется автоматически через рефлексию.
|
||||
//
|
||||
// Что это значит на практике:
|
||||
// - Ошибки — только в рантайме (забыл провайдер → паника при старте)
|
||||
// - Циклическая зависимость → Fx упадёт при старте (тот же тупик)
|
||||
// - Ленивой инициализации нет — всё создаётся сразу
|
||||
//
|
||||
// Fx решает ту же задачу что Wire — «не вычисляй порядок руками».
|
||||
// Но наш DI-контейнер не имеет порядка вообще — каждый геттер
|
||||
// рекурсивно подтягивает зависимости при первом вызове.
|
||||
// Нечему ломаться, нечего сортировать.
|
||||
).Run()
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Обёртки-адаптеры для Fx (fx.Out)
|
||||
// =====================================================
|
||||
// Каждая структура с fx.Out нужна потому что один конкретный тип
|
||||
// реализует несколько интерфейсов для разных потребителей.
|
||||
// Без этих структур Fx не знает как разрулить зависимости.
|
||||
|
||||
// --- database.DB → 3 интерфейса ---
|
||||
|
||||
type dbOut struct {
|
||||
fx.Out
|
||||
|
||||
UserDB repository.UserDB `name:"userDB"`
|
||||
SessionDB repository.SessionDB `name:"sessionDB"`
|
||||
NotificationDB repository.NotificationDB `name:"notificationDB"`
|
||||
}
|
||||
|
||||
func provideDB(cfg *config.Config) (dbOut, error) {
|
||||
db, err := database.New(cfg.DSN)
|
||||
if err != nil {
|
||||
return dbOut{}, err
|
||||
}
|
||||
return dbOut{
|
||||
UserDB: db,
|
||||
SessionDB: db,
|
||||
NotificationDB: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// --- cache.Cache → 2 интерфейса ---
|
||||
|
||||
type cacheOut struct {
|
||||
fx.Out
|
||||
|
||||
SessionCache repository.SessionCache `name:"sessionCache"`
|
||||
AuthCache service.AuthCache `name:"authCache"`
|
||||
}
|
||||
|
||||
func provideCache(cfg *config.Config) cacheOut {
|
||||
c := cache.New(cfg.RedisAddr)
|
||||
return cacheOut{
|
||||
SessionCache: c,
|
||||
AuthCache: c,
|
||||
}
|
||||
}
|
||||
|
||||
// --- events.EventBus → 3 интерфейса ---
|
||||
|
||||
type eventsOut struct {
|
||||
fx.Out
|
||||
|
||||
AuthPublisher service.AuthEventPublisher `name:"authPublisher"`
|
||||
UserPublisher service.UserEventPublisher `name:"userPublisher"`
|
||||
NotifSub service.NotificationEventSubscriber `name:"notifSubscriber"`
|
||||
}
|
||||
|
||||
func provideEvents() eventsOut {
|
||||
bus := events.NewEventBus()
|
||||
return eventsOut{
|
||||
AuthPublisher: bus,
|
||||
UserPublisher: bus,
|
||||
NotifSub: bus,
|
||||
}
|
||||
}
|
||||
|
||||
// --- repository.UserRepo → 2 интерфейса ---
|
||||
|
||||
type userRepoOut struct {
|
||||
fx.Out
|
||||
|
||||
AuthUserRepo service.AuthUserRepository `name:"authUserRepo"`
|
||||
UserRepo service.UserRepository `name:"userRepo"`
|
||||
}
|
||||
|
||||
type userRepoIn struct {
|
||||
fx.In
|
||||
|
||||
DB repository.UserDB `name:"userDB"`
|
||||
}
|
||||
|
||||
func provideUserRepo(in userRepoIn) userRepoOut {
|
||||
repo := repository.NewUserRepo(in.DB)
|
||||
return userRepoOut{
|
||||
AuthUserRepo: repo,
|
||||
UserRepo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
// --- repository.SessionRepo → 1 интерфейс ---
|
||||
|
||||
type sessionRepoIn struct {
|
||||
fx.In
|
||||
|
||||
DB repository.SessionDB `name:"sessionDB"`
|
||||
Cache repository.SessionCache `name:"sessionCache"`
|
||||
}
|
||||
|
||||
func provideSessionRepo(in sessionRepoIn) service.AuthSessionRepository {
|
||||
return repository.NewSessionRepo(in.DB, in.Cache)
|
||||
}
|
||||
|
||||
// --- repository.NotificationRepo → 1 интерфейс ---
|
||||
|
||||
type notificationRepoIn struct {
|
||||
fx.In
|
||||
|
||||
DB repository.NotificationDB `name:"notificationDB"`
|
||||
}
|
||||
|
||||
type notificationRepoOut struct {
|
||||
fx.Out
|
||||
|
||||
Repo service.NotificationRepository `name:"notifRepo"`
|
||||
}
|
||||
|
||||
func provideNotificationRepo(in notificationRepoIn) notificationRepoOut {
|
||||
return notificationRepoOut{
|
||||
Repo: repository.NewNotificationRepo(in.DB),
|
||||
}
|
||||
}
|
||||
|
||||
// --- service.AuthService → 2 интерфейса ---
|
||||
|
||||
type authServiceOut struct {
|
||||
fx.Out
|
||||
|
||||
UserAuthService service.UserAuthService `name:"userAuthService"`
|
||||
APIAuthService api.AuthService `name:"apiAuthService"`
|
||||
}
|
||||
|
||||
type authServiceIn struct {
|
||||
fx.In
|
||||
|
||||
UserRepo service.AuthUserRepository `name:"authUserRepo"`
|
||||
SessionRepo service.AuthSessionRepository
|
||||
Cache service.AuthCache `name:"authCache"`
|
||||
Events service.AuthEventPublisher `name:"authPublisher"`
|
||||
}
|
||||
|
||||
func provideAuthService(in authServiceIn) authServiceOut {
|
||||
svc := service.NewAuthService(in.UserRepo, in.SessionRepo, in.Cache, in.Events)
|
||||
return authServiceOut{
|
||||
UserAuthService: svc,
|
||||
APIAuthService: svc,
|
||||
}
|
||||
}
|
||||
|
||||
// --- service.UserService → 2 интерфейса ---
|
||||
|
||||
type userServiceOut struct {
|
||||
fx.Out
|
||||
|
||||
NotifUserService service.NotificationUserService `name:"notifUserService"`
|
||||
APIUserService api.UserService `name:"apiUserService"`
|
||||
}
|
||||
|
||||
type userServiceIn struct {
|
||||
fx.In
|
||||
|
||||
UserRepo service.UserRepository `name:"userRepo"`
|
||||
AuthService service.UserAuthService `name:"userAuthService"`
|
||||
Events service.UserEventPublisher `name:"userPublisher"`
|
||||
}
|
||||
|
||||
func provideUserService(in userServiceIn) userServiceOut {
|
||||
svc := service.NewUserService(in.UserRepo, in.AuthService, in.Events)
|
||||
return userServiceOut{
|
||||
NotifUserService: svc,
|
||||
APIUserService: svc,
|
||||
}
|
||||
}
|
||||
|
||||
// --- service.NotificationService (fx.Annotate выше, но нужен fx.In для named deps) ---
|
||||
|
||||
type notificationServiceIn struct {
|
||||
fx.In
|
||||
|
||||
Repo service.NotificationRepository `name:"notifRepo"`
|
||||
UserSvc service.NotificationUserService `name:"notifUserService"`
|
||||
Events service.NotificationEventSubscriber `name:"notifSubscriber"`
|
||||
}
|
||||
|
||||
// Заменяем fx.Annotate на отдельный provide — иначе не прокинуть named зависимости.
|
||||
func provideNotificationService(in notificationServiceIn) api.NotificationService {
|
||||
return service.NewNotificationService(in.Repo, in.UserSvc, in.Events)
|
||||
}
|
||||
|
||||
// --- api.Handler (fx.In для named deps) ---
|
||||
|
||||
type handlerIn struct {
|
||||
fx.In
|
||||
|
||||
UserService api.UserService `name:"apiUserService"`
|
||||
AuthService api.AuthService `name:"apiAuthService"`
|
||||
NotificationService api.NotificationService
|
||||
}
|
||||
|
||||
func provideHandler(in handlerIn) api.Handler {
|
||||
return api.NewHandler(in.UserService, in.AuthService, in.NotificationService)
|
||||
}
|
||||
|
||||
func startHTTPServer(handler api.Handler) {
|
||||
srv := &http.Server{
|
||||
Addr: config.AppConfig().HTTPAddr,
|
||||
Handler: handler.Routes(),
|
||||
}
|
||||
|
||||
slog.Info("сервер запущен", "addr", config.AppConfig().HTTPAddr)
|
||||
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
slog.Error("ошибка сервера", "err", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user