Files
di-container/cmd/antipattern-broken/main.go
T
2026-04-13 08:14:09 +03:00

100 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
}