Files

97 lines
4.3 KiB
Markdown
Raw Permalink Normal View History

2026-04-13 08:10:14 +03:00
## 👋 Привет! Я Олег Козырев
Staff Golang Engineer · Ex Ozon, Avito, Tinkoff
📺 [YouTube](https://www.youtube.com/@olezhek28go) · 💬 [Telegram](https://t.me/olezhek28go)
---
# Graceful Shutdown Demo
Учебный Go-проект, демонстрирующий паттерн **graceful shutdown** — корректное завершение приложения с дожиданием текущих запросов и освобождением ресурсов.
## Что внутри
Полноценное HTTP-приложение с несколькими слоями:
- **HTTP API** — три эндпоинта: `/health`, `/users/me`, `/slow`
- **Сервисы** — бизнес-логика (UserService, AuthService, NotificationService)
- **Репозитории** — слой данных (UserRepo, SessionRepo, NotificationRepo)
- **Инфраструктура** — база данных (PostgreSQL), кэш (Redis), шина событий (EventBus)
- **DI-контейнер** — ленивая инициализация зависимостей с автоматической регистрацией в closer
- **Closer** — менеджер graceful shutdown (LIFO-порядок закрытия ресурсов)
## Как работает graceful shutdown
1. `signal.NotifyContext` перехватывает **SIGINT** (Ctrl+C) и **SIGTERM** (Kubernetes)
2. `server.Shutdown()` — перестаёт принимать новые соединения и дожидает текущие запросы (таймаут 15с)
3. `closer.CloseAll()` — закрывает все ресурсы в обратном порядке: кэш, затем БД (таймаут 10с)
4. Суммарный бюджет: 15с + 10с = 25с из 30с Kubernetes grace period
**Паттерн «двойной Ctrl+C»:** первый — graceful shutdown, второй — мгновенное завершение (для разработки, если shutdown завис).
## Как попробовать
```bash
# Запуск
go run cmd/main.go
# В другом терминале — медленный запрос
curl localhost:8080/slow
# Пока запрос висит — нажмите Ctrl+C в терминале сервера
# Без graceful shutdown: curl получит "connection reset by peer"
# С graceful shutdown: curl дождётся ответа "готово!" — 200 OK
```
## Структура проекта
```
cmd/
main.go # Точка входа
internal/
app/
app.go # Жизненный цикл приложения, graceful shutdown
di.go # DI-контейнер с ленивой инициализацией
api/
server.go # HTTP-хендлеры и маршрутизация
closer/
closer.go # Менеджер закрытия ресурсов (LIFO)
config/
config.go # Конфигурация (DSN, Redis, HTTP-порт)
database/
database.go # Абстракция базы данных
cache/
cache.go # Абстракция кэша (Redis)
events/
bus.go # Шина событий (pub/sub)
service/
user.go # Бизнес-логика пользователей
auth.go # Авторизация
notification.go # Уведомления
repository/
user.go # Доступ к данным пользователей
session.go # Доступ к данным сессий
notification.go # Доступ к данным уведомлений
```
## Граф зависимостей
```
Handler
├── UserService
│ ├── UserRepo → DB
│ ├── AuthService
│ │ ├── UserRepo → DB
│ │ ├── SessionRepo → DB + Cache
│ │ ├── Cache
│ │ └── EventBus
│ └── EventBus
├── AuthService
└── NotificationService
├── NotificationRepo → DB
├── UserService
└── EventBus
```
Порядок закрытия (LIFO): EventBus → Cache → DB — база всегда закрывается последней, потому что создаётся первой.