Долго думал, в каком формате это написать, в итоге решил по-простому - рассказать, что есть, и кому это может пригодиться.
Мы с коллегой делали выпускной проект для курсов CloudJava и CloudJava K8S. Это техзадание на бэкенд распределенной системы - четыре микросервиса, Gateway, обвязка. Изначально оно было доступно только участникам курсов, теперь решили открыть бесплатно, без регистрации или “оставьте email”. Просто страница с текстом и тремя OpenAPI-спеками.
Ссылки сразу, чтобы не листать:
Описание проекта: https://javaops.ru/view/cloudjava3
Само ТЗ: https://javaops.ru/view/cloudjava3/rescue-service.html
Дальше - о чем, собственно, проект и почему на него стоит (или не стоит) обратить внимание.
Идея
Бэкенд платформы поиска пропавших людей. Звучит, может, чересчур масштабно для пет-проекта, но в реальности это четыре сервиса со вполне понятными границами.
Сценарий примерно такой. Администратор заводит инцидент - пропал человек, последний раз видели там-то, координаты такие-то. Система ищет волонтеров, которые сейчас ближе всего к этой точке. Достает их контакты, кидает им уведомления. Волонтер отвечает - согласен или нет. Когда поиск завершается (нашли человека или нет - увы, бывает по-разному), всем участникам уходит финальное сообщение.
Сервисы:
Volunteer Service - волонтеры, их профили, статусы, согласия/отказы на инциденты.
Tracking Service - координаты волонтеров и геопоиск. Тут самое “мясо” с PostGIS.
Incident Service - собственно инциденты, оркестрация при их создании.
Notification Service - уведомления. В учебной версии не шлет ничего реального, просто пишет в лог, но придется попотеть, чтобы реализовать сервис так, чтобы перевод на отправку сообщений был с минимальными затратами.
Gateway на Spring Cloud Gateway - единая точка входа, проверка JWT, rate limit.
Чем это интересно технически
Стек, в общем-то, мейнстримный для современного Java-бэкенда. Spring Boot 3.5+, Kafka, PostgreSQL, Keycloak, Kubernetes. Что важно - каждый компонент тут не для галочки.
Расскажу про несколько мест, которые мне самому показались самыми интересными.
Геопоиск. Tracking-сервис должен уметь возвращать волонтеров в прямоугольной области карты. Звучит просто, пока количество точек не переваливает за порог. Дальше нужно отдавать кластеры, а не сами точки. И вот тут начинается: ST_ClusterDBSCAN, расчет параметра eps в зависимости от размера прямоугольника (все в метрах, поэтому ST_Transform и переход в проекцию), minpoints = 1 чтобы одинокий волонтер тоже попадал в выдачу. Это задача, на которой можно реально потренировать PostGIS, а не выучить пару SELECT-ов.
Граница транзакции при создании инцидента. Сценарий такой: записать инцидент в БД, сходить в Tracking за ближайшими волонтерами, потом в Volunteer за их контактами, потом отправить событие в Kafka. В ТЗ требование прямое - сетевых вызовов в границах БД-транзакции быть не должно. И отправки в Kafka - тоже. Дальше думаете сами - Outbox? И если да, то какой - через Debezium или через свой шедулер? Каждый вариант с плюсами и минусами, обоснование своего решения надо зафиксировать в JavaDoc.
Дедупликация консьюмеров. Kafka гарантирует at-least-once, значит, возможны повторы. ТЗ намекает, что можно попробовать настроить exactly_once, но это сложно, дорого и часто не нужно. Намного интереснее - добиться идемпотентности на стороне приложения. Особенно в нотификациях: волонтеру не должно прилететь одно и то же уведомление три раза только потому, что консьюмер перезапустился. Кто работал с Kafka в проде, тот знает, как легко тут “выстрелить себе в ногу”.
Переупорядочивание событий. Это уже про Incident Service, который слушает топик с событиями о согласии/отказе волонтера участвовать в поиске. В редких случаях события приходят не в том порядке, в котором были отправлены. Many-to-many связь между волонтером и инцидентом, плюс возможность отказа после согласия, плюс возможные дубли - и вот вам ловушка, в которую очень легко влететь.
Идемпотентность создания инцидента. Через X-Idempotency-Key. Тут все стандартно, но конкретно реализовать - отдельная история, особенно если хочется сделать честно, а не “нашли ключ - вернули то же самое”.
Параллельная отправка уведомлений под feature-toggle. Предлагается выбрать наиболее оптимальную стратегию распараллеливания отправки сообщений и занести ее реализацию под feature-toggle. Задача далеко не самая тривиальная с учетом того, что необходимо сделать так, чтобы трассировка запросов сохранялась даже в кастомных Executor-ах. Если используете виртуальный потоки или Structured Concurrency, то тут придется отдельно поломать голову.
Что еще в наборе требований:
Аутентификация через Keycloak. Особенность: JWT валидируется на Gateway, дальше в сервисы идет
X-USER-IDизsub. Сами сервисы про OAuth ничего не знают. Решение спорное (Gateway становится единой точкой отказа авторизации, любая внутренняя коммуникация в обход теряет проверки), но оно явно выбрано - и тут есть о чем подумать, почему так.Observability: Prometheus + Grafana + Loki + Tempo. Полный набор. С парой доменных метрик -
incident_count_totalпо регионам,incident_result_totalпо статусам успеха/неудачи.Retry, Circuit Breaker, Rate Limit.
Деплой на выбор: Docker Compose или Kubernetes/Helm. Можно сначала собрать в композе, потом переехать в k8s - нормальный путь.
Мелочи, которые часто забывают: маскирование PII в логах (
s***l@service.com), Flyway/Liquibase, UTC везде, очистка устаревших данных самим приложением.
Кому это вообще надо
Честно скажу - не всем. Это не первый пет-проект на Spring.
Подойдет, если:
Уверенно владеете Spring Boot, Spring Data, понимаете, как устроен Docker. На работе делаете типовые задачи и хотите выйти за их пределы.
Готовитесь к собеседованиям уровня middle+/senior и хотите портфолио, где есть про что говорить.
Не боитесь Kafka и PostgreSQL, хотите на практике реализовать Transactional Outbox.
Есть теоретическое понимание микросервисной архитектуры и основных паттернов, но нет опыта их самостоятельной реализации.
Не подойдет, если:
Только-только освоили Spring и ищете что-то “на потренироваться”. Тут, скорее всего, вы захлебнетесь и забросите. Я бы рекомендовал взять что-то попроще.
Ищете “проект на выходные”. Объем проекта - это два-три двухнедельных спринта по меркам продуктовой команды. У меня ушло около 2 месяцев кодирования по вечерам после основной работы.
Что бесплатно
Собственно говоря - все то, ради чего я этот пост и пишу:
Полное ТЗ. Со всеми сценариями, описанием примерных моделей данных, схемами сообщений в Avro, нефункциональными требованиями.
Три OpenAPI-спеки (volunteer, tracking, incident). Сразу можно генерировать клиенты и серверные стабы.
Архитектурная диаграмма.
Советы по реализации и декомпозиции на подзадачи.
Этого хватит, чтобы реализовать проект целиком. Контракты зафиксированы - сравнивайте свою реализацию с OpenAPI, доверяйте здравому смыслу там, где документация молчит.
Можно прекрасно делать проект и без платной части. Просто проверять придется самим, и сравнить будет не с чем. Для многих этого достаточно. Для кого-то - нет, и тогда есть платный вариант:
Скрытый текст
Набор end-to-end автотестов. Объективная приемка: прошли - сделали, не прошли - где-то ошиблись.
Референсная реализация автора курса. Чтобы сверить свои архитектурные решения с чужими.
Ревью каждого сервиса отдельно.
PS
Если будете делать и наткнетесь на места, где, на ваш взгляд, ТЗ непонятное, кривое или вообще не покрывает кейс - напишите мне или в комментариях. ТЗ открытое, и я готов его дорабатывать. Это, в общем-то, еще одна причина выложить его публично - собрать фидбэк от тех, кто на нем что-то реальное сделал.
На этом все. Удачи тем, кто решится. И, надеюсь, будет полезно!