graph TB
HTTP[HTTP Request] --> Chi[Chi Router]
Chi --> MW[Middleware: Auth, CORS, Rate Limit, Logger]
MW --> H[Handler]
H --> S[Service: бизнес-логика]
S --> R[Repository: PostgreSQL/pgx]
S --> C[Cache: Redis]
S --> FS[File Storage: MinIO/S3]
R --> PG[(PostgreSQL + PostGIS)]
| Паттерн | Где реализовано | Заметка |
|---|---|---|
| Sentinel errors | services/*.go |
[[atomic-sentinel-errors-go]] |
| Dependency Injection (интерфейсы) | services/*.go, main.go |
[[atomic-dependency-inversion-go]] |
| Refresh token rotation | services/auth.go |
[[decision-jwt-refresh-storage]] |
| Rate limiting (Redis + fallback) | middleware/ratelimit_redis.go |
[[decision-rate-limiter-redis]] |
| WebSocket origin check | handlers/websocket.go |
[[atomic-websocket-origin-check]] |
Refresh token race fix (DeleteIfExists) |
services/auth.go |
[[atomic-refresh-token-race-condition]] |
| SSRF protection via MaxBytesReader | handlers/*.go |
http.MaxBytesReader(w, r.Body, 1MB) |
| Go-playground/validator | validator/validator.go |
[[decision-validation-library]] |
| Cursor-based pagination | services/places.go |
base64 cursor, limit+1 trick |
| Graceful shutdown | cmd/api/main.go |
signal.Notify + server.Shutdown с 30s timeout |
| Fail-open/Fail-closed rate limiter | main.go:119 |
[[atomic-redis-rate-limiter-failopen-failclosed]] |
| CSP nonce hardening | Caddyfile |
[[atomic-csp-hardening]] |
| Package-level mutable state (anti-pattern) | handlers/errors.go:13 |
[[atomic-global-appenv-package-var]] |
| TOCTOU race (anti-pattern) | services/auth.go:85-86 |
[[atomic-toctou-race-registration]] |
sequenceDiagram
Handler->>Service: call
Service->>Repo: query
Repo-->>Service: pgx error
Service-->>Handler: sentinel error (%w)
Handler->>Handler: errors.Is() -> HTTP status
alt production
Handler->>Handler: скрыть детали 500
else development
Handler->>Handler: вернуть как есть
end
Все хендлеры, принимающие JSON, обёрнуты в http.MaxBytesReader(w, r.Body, 1MB). Это предотвращает атаки исчерпания памяти через гигантские payloads.
Обнаружена неконсистентность интерфейса MapProvider: init() принимает [lng, lat], setCenter() — [lat, lng]. См. [[atomic-map-coordinates-inconsistency]].
В PlaceForm.tsx, MapView.tsx, useWebSocket.ts ошибки подавляются пустым catch. См. [[atomic-error-swallowing-frontend]].
| Уровень | Инструмент | Примеры |
|---|---|---|
| Unit (сервисы) | testing + моки |
auth_test.go (6 тестов), places_test.go (3 теста) |
| Integration | pgxpool + test DB |
place_repo_test.go (4 теста) |
| Frontend | Vitest + testing-library | ErrorBoundary.test.tsx (2 теста) |
Покрытие: ~5%. Нужно расширять — особенно хендлеры и middleware.
| Антипаттерн | Место | Заметка |
|---|---|---|
| Package-level mutable state | handlers/errors.go:13 |
[[atomic-global-appenv-package-var]] |
| TOCTOU race | services/auth.go:85-86 |
[[atomic-toctou-race-registration]] |
| Mutex без defer | middleware/ratelimit.go:61 |
[[atomic-mutex-defer-panic]] |
| N+1 запросы | services/places.go:112-136 |
[[atomic-n-plus-one-getbyid]] |
| Silenced errors | PlaceForm.tsx:42-43 |
[[atomic-error-swallowing-frontend]] |
#architecture #backend #golang #MOC #best-practice #anti-pattern