This commit is contained in:
2026-04-13 08:14:09 +03:00
commit 0449337ae7
39 changed files with 2726 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
package main
// =====================================================
// Общие интерфейсы — один пакет, все потребители импортируют.
// Как в Java/Spring: один UserRepository на весь проект.
//
// Каждый интерфейс определён ОДИН РАЗ и переиспользуется всеми.
// В отличие от Go-идиомы, где каждый потребитель определяет свой.
// =====================================================
// Инфраструктура
type DB interface {
Query(query string) error
QueryRow(query string) error
Exec(query string) error
BeginTx() error
BulkInsert(query string, args ...any) error
}
type Cache interface {
Get(key string) (string, error)
Set(key, value string) error
}
type EventBus interface {
Publish(event string)
Subscribe(event string, handler func())
}
// Репозитории
type UserRepository interface{}
type SessionRepository interface{}
type NotificationRepository interface{}
// Сервисы
type AuthService interface{}
type UserService interface{}
type NotificationService interface{}
+79
View File
@@ -0,0 +1,79 @@
package main
// Fx с ОБЩИМИ интерфейсами — чистый и простой пример.
//
// Сравните:
// cmd/fx-di/main.go — consumer-defined interfaces (Go-идиома) → 330 строк
// cmd/fx-di-shared/ — shared interfaces (Java-подход) → вот этот файл
//
// Разница — только в подходе к определению интерфейсов.
// Когда один тип → один интерфейс, Fx работает без fx.Out/fx.In/named-тегов.
//
// Это НЕ рекомендация так писать на Go. Это демонстрация того,
// под какую архитектуру Fx был спроектирован.
//
// Lifecycle:
// Fx управляет жизненным циклом компонентов через хуки OnStart/OnStop.
// При запуске — вызывает OnStart в порядке зависимостей (сначала DB, потом сервисы).
// При остановке — вызывает OnStop в обратном порядке (сначала сервер, потом DB).
// Отправьте SIGINT (Ctrl+C) чтобы увидеть порядок остановки в логах.
//
// Хотите увидеть как это масштабируется на несколько команд?
// Смотрите cmd/fx-di-modules/ — тот же код, но каждый домен в своём файле.
import (
"context"
"log/slog"
"net"
"net/http"
"go.uber.org/fx"
)
func main() {
fx.New(
fx.Provide(
newDB,
newCache,
newEventBus,
newUserRepo,
newSessionRepo,
newNotificationRepo,
newAuthService,
newUserService,
newNotificationService,
newHandler,
),
fx.Invoke(startHTTPServer),
// Вот и всё. 10 провайдеров, 1 invoke. Fx разрулил сам.
// Потому что один тип → один интерфейс → нет неоднозначности.
//
// А lifecycle хуки (OnStart/OnStop) — внутри провайдеров newDB, newCache
// и startHTTPServer. Fx сам вызовет их в правильном порядке.
).Run()
}
func startHTTPServer(lc fx.Lifecycle, h *handler) {
srv := &http.Server{
Addr: ":8080",
Handler: h.routes(),
}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
ln, err := net.Listen("tcp", srv.Addr)
if err != nil {
return err
}
slog.Info("HTTP-сервер запущен", "addr", srv.Addr)
go srv.Serve(ln)
return nil
},
OnStop: func(ctx context.Context) error {
slog.Info("HTTP-сервер останавливается...")
return srv.Shutdown(ctx)
},
})
}
+175
View File
@@ -0,0 +1,175 @@
package main
import (
"context"
"fmt"
"log/slog"
"net/http"
"go.uber.org/fx"
)
// =====================================================
// Реализации — конструкторы возвращают интерфейсы.
// Один тип → один интерфейс → Fx разруливает автоматически.
//
// DB и Cache используют fx.Lifecycle для регистрации хуков:
// OnStart — пинг/проверка соединения при запуске
// OnStop — корректное закрытие при остановке
//
// Fx сам вызывает хуки в правильном порядке:
// Старт: DB → Cache → ... → HTTP-сервер
// Остановка: HTTP-сервер → ... → Cache → DB
// =====================================================
// --- Инфраструктура ---
type dbImpl struct{ dsn string }
func (db *dbImpl) Query(query string) error { return nil }
func (db *dbImpl) QueryRow(query string) error { return nil }
func (db *dbImpl) Exec(query string) error { return nil }
func (db *dbImpl) BeginTx() error { return nil }
func (db *dbImpl) BulkInsert(query string, args ...any) error { return nil }
func (db *dbImpl) Close() error {
slog.Info("БД: соединение закрыто")
return nil
}
func newDB(lc fx.Lifecycle) (DB, error) {
db := &dbImpl{dsn: "postgres://localhost:5432/demo"}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
slog.Info("БД: подключение установлено", "dsn", db.dsn)
return nil // здесь был бы db.Ping(ctx)
},
OnStop: func(ctx context.Context) error {
return db.Close()
},
})
return db, nil
}
type cacheImpl struct{}
func (c *cacheImpl) Get(key string) (string, error) { return "", nil }
func (c *cacheImpl) Set(key, value string) error { return nil }
func (c *cacheImpl) Close() error {
slog.Info("Кэш: соединение закрыто")
return nil
}
func newCache(lc fx.Lifecycle) Cache {
c := &cacheImpl{}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
slog.Info("Кэш: подключение установлено")
return nil // здесь был бы c.Ping(ctx)
},
OnStop: func(ctx context.Context) error {
return c.Close()
},
})
return c
}
type eventBusImpl struct{}
func (b *eventBusImpl) Publish(event string) { slog.Info("событие", "event", event) }
func (b *eventBusImpl) Subscribe(event string, handler func()) { slog.Info("подписка", "event", event) }
func newEventBus(lc fx.Lifecycle) EventBus {
bus := &eventBusImpl{}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
slog.Info("EventBus: запущен")
return nil
},
OnStop: func(ctx context.Context) error {
slog.Info("EventBus: очередь событий дренирована")
return nil
},
})
return bus
}
// --- Репозитории ---
type userRepo struct{ db DB }
type sessionRepo struct {
db DB
cache Cache
}
type notificationRepo struct{ db DB }
func newUserRepo(db DB) UserRepository {
return &userRepo{db: db}
}
func newSessionRepo(db DB, cache Cache) SessionRepository {
return &sessionRepo{db: db, cache: cache}
}
func newNotificationRepo(db DB) NotificationRepository {
return &notificationRepo{db: db}
}
// --- Сервисы ---
type authServiceImpl struct {
userRepo UserRepository
sessionRepo SessionRepository
cache Cache
events EventBus
}
func newAuthService(userRepo UserRepository, sessionRepo SessionRepository, cache Cache, events EventBus) AuthService {
return &authServiceImpl{userRepo: userRepo, sessionRepo: sessionRepo, cache: cache, events: events}
}
type userServiceImpl struct {
userRepo UserRepository
authService AuthService
events EventBus
}
func newUserService(userRepo UserRepository, authService AuthService, events EventBus) UserService {
return &userServiceImpl{userRepo: userRepo, authService: authService, events: events}
}
type notificationServiceImpl struct {
notificationRepo NotificationRepository
userService UserService
events EventBus
}
func newNotificationService(notificationRepo NotificationRepository, userService UserService, events EventBus) NotificationService {
return &notificationServiceImpl{notificationRepo: notificationRepo, userService: userService, events: events}
}
// --- Handler ---
type handler struct {
userService UserService
authService AuthService
notificationService NotificationService
}
func newHandler(userService UserService, authService AuthService, notificationService NotificationService) *handler {
return &handler{userService: userService, authService: authService, notificationService: notificationService}
}
func (h *handler) routes() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "ok")
})
return mux
}