## 👋 Привет! Я Олег Козырев 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 — база всегда закрывается последней, потому что создаётся первой.