Как реализовать общение с партнером на колбеках
Что проверяют
Под «общением на колбеках» обычно имеют в виду интеграцию, где партнёр отвечает не сразу, а позже вызывает ваш эндпоинт. Проверяют, продумали ли вы надёжность: потерянный колбек, повторный колбек, поддельный колбек. Следом спрашивают, что делать, если колбек так и не пришёл.
Как отвечать
Схема по шагам:
- исходящий запрос: генерируете свой идентификатор операции, сохраняете её в базе со статусом
PENDINGи только потом вызываете партнёра. Идентификатор передаёте как ключ идемпотентности, чтобы повтор запроса не создал вторую операцию; - приём колбека: отдельный эндпоинт проверяет подпись (обычно HMAC от тела с общим секретом, сравнение через
MessageDigest.isEqual, чтобы не открыть атаку по времени) и метку времени против повторного воспроизведения; - быстрый ответ: эндпоинт сохраняет событие и сразу отвечает 200, а бизнес-обработка идёт асинхронно через очередь. Иначе у партнёра сработает таймаут, и он пришлёт колбек ещё раз;
- идемпотентность: партнёр почти всегда доставляет «как минимум один раз». Дубликаты отсекает уникальный ключ в базе, а переход статуса делается условным
UPDATE ... WHERE status = 'PENDING', чтобы запоздавший колбек не откатил финальное состояние; - сверка: планировщик находит операции, которые висят в
PENDINGдольше порога, и опрашивает статус у партнёра. Плюс сверка реестров, если партнёр её даёт.
Если вопрос про колбеки внутри кода, вместо своего интерфейса с onSuccess и onFailure берут CompletableFuture: thenApply, exceptionally, orTimeout() и явный Executor. Асинхронный HTTP-вызов даёт HttpClient.sendAsync(), а на Java 21 с виртуальными потоками часто проще писать обычный блокирующий код.
Где спотыкаются
Создают new Thread() на каждый вызов. Под нагрузкой это тысячи потоков, нужен пул или виртуальные потоки.
Вызывают onFailure из catch, который оборачивает и вызов onSuccess. Если упал сам обработчик успеха, клиент получит оба колбека.
Проверяют асинхронный код через assert внутри колбека. Тест завершается раньше, чем колбек сработает, а assert без флага -ea выключен. Нужны CompletableFuture.get с таймаутом, CountDownLatch или Awaitility.