100 lines
3.7 KiB
Go
100 lines
3.7 KiB
Go
|
|
package main
|
|||
|
|
|
|||
|
|
// Сломанный антипаттерн: тот же код, но строки переставлены.
|
|||
|
|
//
|
|||
|
|
// Сценарий: два разработчика параллельно добавляли сервисы,
|
|||
|
|
// при мерже строки перемешались. Код скомпилировался.
|
|||
|
|
// go build прошёл. CI прошёл (юнит-тесты не трогают main.go).
|
|||
|
|
// Деплой. Первый запрос — паника.
|
|||
|
|
//
|
|||
|
|
// Запустите: go run ./cmd/antipattern-broken/
|
|||
|
|
// curl http://localhost:8080/health → 200 ok (всё «работает»)
|
|||
|
|
// curl http://localhost:8080/users/me → nil pointer dereference (ПАНИКА)
|
|||
|
|
//
|
|||
|
|
// /health не трогает зависимости — поэтому проходит.
|
|||
|
|
// /users/me вызывает userService.GetProfile() → authService.ValidateToken() → nil!
|
|||
|
|
|
|||
|
|
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)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// === Репозитории и сервисы ===
|
|||
|
|
// После мержа строки перемешались. Всё компилируется — Go проверяет
|
|||
|
|
// только что переменная ОБЪЯВЛЕНА, а не что внутри валидное значение.
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
c cache.Cache
|
|||
|
|
userRepo repository.UserRepo
|
|||
|
|
sessionRepo repository.SessionRepo
|
|||
|
|
notificationRepo repository.NotificationRepo
|
|||
|
|
eventBus events.EventBus
|
|||
|
|
authService service.AuthService
|
|||
|
|
userService service.UserService
|
|||
|
|
notificationService service.NotificationService
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
userRepo = repository.NewUserRepo(db)
|
|||
|
|
notificationRepo = repository.NewNotificationRepo(db)
|
|||
|
|
|
|||
|
|
// ⚠️ ПРОБЛЕМА 1: sessionRepo создаётся ДО cache.
|
|||
|
|
// cache = nil → sessionRepo хранит nil вместо кэша.
|
|||
|
|
sessionRepo = repository.NewSessionRepo(db, c) // c == nil!
|
|||
|
|
|
|||
|
|
c = cache.New(cfg.RedisAddr) // поздно — sessionRepo уже создан с nil
|
|||
|
|
|
|||
|
|
eventBus = events.NewEventBus()
|
|||
|
|
|
|||
|
|
// ⚠️ ПРОБЛЕМА 2: userService создаётся ДО authService.
|
|||
|
|
// authService = nil → userService хранит nil вместо сервиса авторизации.
|
|||
|
|
userService = service.NewUserService(userRepo, authService, eventBus) // authService == nil!
|
|||
|
|
|
|||
|
|
authService = service.NewAuthService(userRepo, sessionRepo, c, eventBus) // поздно
|
|||
|
|
|
|||
|
|
notificationService = service.NewNotificationService(
|
|||
|
|
notificationRepo,
|
|||
|
|
userService,
|
|||
|
|
eventBus,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// === Сервер ===
|
|||
|
|
// Всё скомпилировалось. go vet пройдёт. Линтер пройдёт.
|
|||
|
|
// Но внутри userService лежит nil вместо authService.
|
|||
|
|
// Первый запрос, который дёрнет авторизацию — паника.
|
|||
|
|
|
|||
|
|
handler := api.NewHandler(userService, authService, notificationService)
|
|||
|
|
srv := &http.Server{
|
|||
|
|
Addr: cfg.HTTPAddr,
|
|||
|
|
Handler: handler.Routes(),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
slog.Info("сервер запущен", "addr", cfg.HTTPAddr)
|
|||
|
|
|
|||
|
|
if err = srv.ListenAndServe(); err != nil {
|
|||
|
|
slog.Error("ошибка сервера", "err", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_ = db.Close()
|
|||
|
|
_ = c.Close()
|
|||
|
|
}
|