Как мы укротили NATS в Go: Представляем deez-nats — наш ответ на сложность микросервисов
От разработчиков для разработчиков: почему мы устали писать бойлерплейт и создали свой фреймворк.
В нашей архитектуре мы делаем большую ставку на NATS. Это фантастическая технология: быстрая, надежная, с поддержкой как простых Pub/Sub (Core), так и персистентных стримов (JetStream). Но по мере роста количества микросервисов мы столкнулись с проблемой, знакомой многим go-разработчикам.
Мы тратили 30% времени на бизнес-логику и 70% — на "обвязку" NATS. Бесконечные nc.Subscribe, ручной маршалинг JSON, обработка ошибок, управление таймаутами в RPC-вызовах... Код раздувался, а читаемость падала.
Нам нужен был инструмент, который бы давал удобство HTTP-фреймворков (вроде Gin или Echo), но для асинхронного месседжинга. Не найдя идеального решения, мы написали своё.
Встречайте deez-nats — библиотеку, которая превращает работу с NATS в удовольствие.
Философия deez-nats
Когда мы проектировали эту библиотеку, мы ставили во главу угла три принципа:
- Developer Experience (DX): API должен быть интуитивным.
- Type Safety: Мы любим Go за типизацию, поэтому библиотека активно использует дженерики (Generics).
- Production Ready: Graceful shutdown, middleware и контексты — это база, а не опция.
Давайте посмотрим, как это работает на практике.
Установка и Базовая настройка
Всё начинается стандартно. Библиотека требует Go 1.21+, так как мы используем современные фичи языка.
В точке входа (main.go) мы инициализируем два основных сервиса: один для RPC (синхронные вызовы request-reply), другой — для событий.
RPC: Забываем о ручном Publish
В чистом NATS реализация паттерна Request-Reply требует ручного управления Reply топиком. Мы автоматизировали это через абстракцию RPCContext.
Вот как выглядит типичный обработчик в нашем продакшене:
А вот как мы вызываем этот метод из другого микросервиса:
Никаких таймаутов подписки, никаких interface{}. Чистый, типизированный вызов.
Middleware и Группировка: Организуем код правильно
Когда проект растет, сваливать все маршруты в кучу — плохая идея. Мы внедрили концепцию Групп и Middleware, вдохновленную HTTP-роутерами.
Это позволяет нам, например, добавить логирование или проверку прав доступа только для определенной группы команд.
JetStream и Типизированные События
Работа с JetStream (JS) сложнее, чем с Core NATS, из-за необходимости управлять Consumer'ами и Ack-политиками. Мы постарались скрыть эту сложность, оставив гибкость.
Мы используем дженерики для строгой типизации событий. Больше никаких опечаток в названиях полей JSON.
Килер-фича: Библиотека сама управляет Ack. Если ваш хендлер вернул ошибку, будет отправлен Nak (отрицательное подтверждение), и сообщение будет доставлено повторно согласно политикам JetStream.
Graceful Shutdown: Выходим красиво
В мире Kubernetes поды умирают и рождаются постоянно. Нельзя просто "убить" процесс — нужно дать текущим обработчикам завершить работу.
deez-nats реализует правильный паттерн завершения. Метод Shutdown:
- Перестает принимать новые сообщения.
- Ждет завершения активных хендлеров.
- Закрывает подписки и соединение.
Итог
Мы создали deez-nats не как учебный проект, а как инструмент для решения реальных проблем в продакшене. Он позволил нам сократить объем кода в микросервисах, уменьшить количество ошибок, связанных с типами, и стандартизировать подход к общению между сервисами.
Библиотека поддерживает кастомные маршалеры (хотите Protobuf вместо JSON? Пожалуйста!) и полностью совместима с нативным клиентом nats.go.
Попробуйте внедрить её в свой проект: GitHub: leinodev/deez-nats
Будем рады вашим звездам, ишью и пулл-реквестам. Давайте делать Go-экосистему лучше вместе! 🥜