Как организовать асинхронное взаимодействие между репорт сервисом и PDF сервисом
Что проверяют
Умеете ли вы спроектировать долгую операцию так, чтобы вызывающий не ждал её в HTTP-запросе, и продумать надёжность: повторы, дубли, падение сервиса посреди генерации. Следом спрашивают, как пользователь узнает, что PDF готов.
Как отвечать
Каркас схемы:
- приём задачи: репорт-сервис создаёт задание со статусом
PENDINGиjobIdи сразу отвечает клиенту202 Accepted; - передача через брокер: команда с
jobIdи данными отчёта (или ссылкой на них) уходит в Kafka или RabbitMQ. В Spring этоKafkaTemplateи@KafkaListenerилиRabbitTemplateи@RabbitListener. Чтобы запись задания и отправка не разошлись при сбое, публикуют через outbox; - обработка: PDF-сервис генерирует файл, кладёт в S3-совместимое хранилище и публикует событие
PdfReadyсо ссылкой. Сам файл в сообщение не кладут: Kafka по умолчанию ограничивает сообщение примерно мегабайтом, да и брокер не хранилище; - обратная связь: репорт-сервис слушает
PdfReadyи обновляет статус. Клиент узнаёт о готовности опросомGET /jobs/{id}, через SSE или WebSocket, либо по вебхуку; - надёжность: доставка at-least-once, поэтому PDF-сервис идемпотентен по
jobId. Временные ошибки повторяются с backoff (@RetryableTopicв Spring Kafka или retry-политика слушателя), после исчерпания сообщение уходит в DLQ, задание вFAILED; - масштабирование: несколько экземпляров PDF-сервиса в одной consumer group, параллелизм ограничен числом партиций. Генерация тяжёлая по памяти, число одновременных задач на инстанс стоит ограничить.
Если оба модуля живут в одном приложении, хватит @Async или CompletableFuture с отдельным пулом, но при рестарте задачи потеряются.
Где спотыкаются
Делают синхронный REST-вызов с большим таймаутом и называют это асинхронностью, потому что внутри CompletableFuture.
Не продумывают дубли: сообщение пришло дважды, пользователь получил два одинаковых PDF и два письма.
Забывают про корреляцию и таймауты. Без jobId непонятно, чей это файл, а упавшее задание висит в PENDING вечно.