import
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
// API-слой — HTTP-хендлер и сервер.
|
||||
// Собирает сервисы из всех доменов в единый HTTP-интерфейс.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
var APIModule = fx.Module("api",
|
||||
fx.Provide(newHandler),
|
||||
fx.Invoke(startHTTPServer),
|
||||
)
|
||||
|
||||
// --- 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
|
||||
}
|
||||
|
||||
// --- HTTP Server ---
|
||||
|
||||
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)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
// Домен авторизации.
|
||||
// Этим файлом владеет команда авторизации.
|
||||
//
|
||||
// Что внутри: сессии, проверка токенов, логин/логаут.
|
||||
// Что НЕ знает: как устроены уведомления, как устроены профили.
|
||||
//
|
||||
// Добавить TokenValidator? Правим ТОЛЬКО этот файл:
|
||||
// 1. Добавляем тип + конструктор
|
||||
// 2. Добавляем fx.Provide(newTokenValidator) в AuthModule
|
||||
// 3. main.go не трогаем — ноль мерж-конфликтов
|
||||
|
||||
import "go.uber.org/fx"
|
||||
|
||||
var AuthModule = fx.Module("auth",
|
||||
fx.Provide(
|
||||
newSessionRepo,
|
||||
newAuthService,
|
||||
// newTokenValidator, ← команда добавит сюда, не в main.go
|
||||
),
|
||||
)
|
||||
|
||||
// --- SessionRepo ---
|
||||
|
||||
type sessionRepo struct {
|
||||
db DB
|
||||
cache Cache
|
||||
}
|
||||
|
||||
func newSessionRepo(db DB, cache Cache) SessionRepository {
|
||||
return &sessionRepo{db: db, cache: cache}
|
||||
}
|
||||
|
||||
// --- AuthService ---
|
||||
|
||||
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}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
// Общая инфраструктура: БД, кэш, шина событий.
|
||||
// Этим владеет платформенная команда (или DevOps).
|
||||
// Бизнес-команды используют, но не трогают.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
var InfraModule = fx.Module("infra",
|
||||
fx.Provide(newDB, newCache, newEventBus),
|
||||
)
|
||||
|
||||
// --- 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
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return db.Close()
|
||||
},
|
||||
})
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// --- Cache ---
|
||||
|
||||
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
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return c.Close()
|
||||
},
|
||||
})
|
||||
return c
|
||||
}
|
||||
|
||||
// --- EventBus ---
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
// Общие интерфейсы — один пакет, все потребители импортируют.
|
||||
// Как в Java/Spring: один UserRepository на весь проект.
|
||||
// fx.Module работает красиво именно с таким подходом.
|
||||
|
||||
// Инфраструктура
|
||||
|
||||
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{}
|
||||
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
// fx.Module — модульный монолит с Fx.
|
||||
//
|
||||
// Каждый бизнес-домен живёт в своём файле:
|
||||
// infra.go — БД, кэш, шина событий (общая инфраструктура)
|
||||
// auth_module.go — авторизация: сессии + проверка токенов
|
||||
// user_module.go — профили пользователей
|
||||
// notification_module.go — уведомления
|
||||
// api.go — HTTP-хендлер и сервер
|
||||
//
|
||||
// Зачем это нужно:
|
||||
// Команда авторизации добавляет TokenValidator → правит ТОЛЬКО auth_module.go.
|
||||
// main.go не трогает. Конфликтов при мерже с командой уведомлений — ноль.
|
||||
//
|
||||
// Без fx.Module все провайдеры в одном main.go. Три команды правят один файл.
|
||||
// Мерж-конфликты на каждом PR.
|
||||
//
|
||||
// Сравните с cmd/fx-di-shared/ — тот же код, но всё в одном файле.
|
||||
|
||||
import "go.uber.org/fx"
|
||||
|
||||
func main() {
|
||||
fx.New(
|
||||
// Общая инфраструктура
|
||||
InfraModule,
|
||||
|
||||
// Бизнес-домены — каждая команда владеет своим файлом.
|
||||
// Добавить новый домен = новый файл + одна строка здесь.
|
||||
AuthModule,
|
||||
UserModule,
|
||||
NotificationModule,
|
||||
|
||||
// API — собирает сервисы в HTTP-хендлер
|
||||
APIModule,
|
||||
).Run()
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
// Домен уведомлений.
|
||||
// Этим файлом владеет команда уведомлений.
|
||||
//
|
||||
// Что внутри: отправка уведомлений, шаблоны, каналы доставки.
|
||||
// Что НЕ знает: как устроена авторизация, как устроены профили внутри.
|
||||
|
||||
import "go.uber.org/fx"
|
||||
|
||||
var NotificationModule = fx.Module("notification",
|
||||
fx.Provide(
|
||||
newNotificationRepo,
|
||||
newNotificationService,
|
||||
),
|
||||
)
|
||||
|
||||
// --- NotificationRepo ---
|
||||
|
||||
type notificationRepo struct{ db DB }
|
||||
|
||||
func newNotificationRepo(db DB) NotificationRepository {
|
||||
return ¬ificationRepo{db: db}
|
||||
}
|
||||
|
||||
// --- NotificationService ---
|
||||
|
||||
type notificationServiceImpl struct {
|
||||
notificationRepo NotificationRepository
|
||||
userService UserService
|
||||
events EventBus
|
||||
}
|
||||
|
||||
func newNotificationService(notificationRepo NotificationRepository, userService UserService, events EventBus) NotificationService {
|
||||
return ¬ificationServiceImpl{notificationRepo: notificationRepo, userService: userService, events: events}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
// Домен профилей пользователей.
|
||||
// Этим файлом владеет команда профилей.
|
||||
//
|
||||
// Что внутри: пользователи, профили, настройки.
|
||||
// Что НЕ знает: как устроена авторизация внутри, как устроены уведомления.
|
||||
|
||||
import "go.uber.org/fx"
|
||||
|
||||
var UserModule = fx.Module("user",
|
||||
fx.Provide(
|
||||
newUserRepo,
|
||||
newUserService,
|
||||
),
|
||||
)
|
||||
|
||||
// --- UserRepo ---
|
||||
|
||||
type userRepo struct{ db DB }
|
||||
|
||||
func newUserRepo(db DB) UserRepository {
|
||||
return &userRepo{db: db}
|
||||
}
|
||||
|
||||
// --- UserService ---
|
||||
|
||||
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}
|
||||
}
|
||||
Reference in New Issue
Block a user