[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"question-rest-api-chto-takoe-asinkhronnye-api-i-kak-ikh-proektirovat":3},{"id":4,"slug":5,"topicId":6,"topicSlug":7,"topicName":8,"topicEmoji":9,"question":10,"answer":11,"codeLang":12,"codeSrc":12,"important":12,"commonMistakes":12,"modernUsage":12,"difficulty":13,"tags":14,"related":16,"progress":17,"seo":18},1232,"chto-takoe-asinkhronnye-api-i-kak-ikh-proektirovat",34,"rest-api","REST API","🌐","Что такое асинхронные API и как их проектировать?","Асинхронные API — подходы к взаимодействию клиента и сервера, при которых обработка запроса не блокирует клиента и результат доставляется позже (через callback, polling или push-уведомление).\n\n### Когда нужны\n\n- Долгие операции — генерация отчёта, обработка видео, массовый импорт.\n- Real-time обновления — чат, биржевые котировки, уведомления.\n- Событийные уведомления — платёж завершён, заказ отправлен.\n- Интеграции — webhook-и от внешних систем (Stripe, GitHub).\n\n### Сравнение подходов\n\n| Характеристика | Polling | Webhooks | SSE | WebSocket |\n|---------------|---------|----------|-----|-----------|\n| Направление | Клиент -> Сервер | Сервер -> Клиент | Сервер -> Клиент | Двунаправленный |\n| Протокол | HTTP | HTTP | HTTP | WS (поверх HTTP) |\n| Задержка | Высокая (интервал) | Низкая | Низкая | Очень низкая |\n| Нагрузка на сервер | Высокая | Низкая | Средняя | Средняя |\n| Масштабирование | Простое | Простое | Сложнее | Сложнее |\n| Подходит для | Долгие операции | Интеграции, события | Уведомления, ленты | Чат, real-time игры |\n\n### Паттерн 1: Polling (202 Accepted + Location)\n\n\u003Cdetails>\u003Csummary>Реализация\u003C\u002Fsummary>\n\n```java\n\u002F\u002F Запуск долгой операции\n@PostMapping(\"\u002Fapi\u002Freports\")\npublic ResponseEntity\u003CTaskStatus> createReport(\n        @Valid @RequestBody ReportRequest request) {\n    String taskId = reportService.startGeneration(request);\n\n    URI statusUri = URI.create(\"\u002Fapi\u002Freports\u002Ftasks\u002F\" + taskId);\n    TaskStatus status = new TaskStatus(taskId, \"PENDING\", null);\n\n    return ResponseEntity.accepted()\n        .location(statusUri)\n        .header(\"Retry-After\", \"5\")\n        .body(status);\n}\n\n\u002F\u002F Проверка статуса\n@GetMapping(\"\u002Fapi\u002Freports\u002Ftasks\u002F{taskId}\")\npublic ResponseEntity\u003CTaskStatus> getTaskStatus(@PathVariable String taskId) {\n    TaskStatus status = reportService.getStatus(taskId);\n\n    if (\"COMPLETED\".equals(status.status())) {\n        return ResponseEntity.status(HttpStatus.SEE_OTHER)\n            .location(URI.create(status.resultUrl()))\n            .body(status);\n    }\n    if (\"FAILED\".equals(status.status())) {\n        return ResponseEntity.ok(status);\n    }\n    return ResponseEntity.ok()\n        .header(\"Retry-After\", \"5\")\n        .body(status);\n}\n\npublic record TaskStatus(\n    String taskId,\n    String status,       \u002F\u002F PENDING, PROCESSING, COMPLETED, FAILED\n    String resultUrl,\n    Integer progress,\n    String errorMessage\n) {\n    public TaskStatus(String taskId, String status, String resultUrl) {\n        this(taskId, status, resultUrl, null, null);\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n### Паттерн 2: Webhooks (Callback URL)\n\n\u003Cdetails>\u003Csummary>Реализация\u003C\u002Fsummary>\n\n```java\n@PostMapping(\"\u002Fapi\u002Fwebhooks\")\npublic ResponseEntity\u003CWebhookRegistration> registerWebhook(\n        @Valid @RequestBody WebhookRequest request) {\n    WebhookRegistration registration = webhookService.register(request);\n    return ResponseEntity.status(HttpStatus.CREATED).body(registration);\n}\n\npublic record WebhookRequest(\n    @NotBlank String url,           \u002F\u002F URL для callback\n    @NotEmpty Set\u003CString> events,   \u002F\u002F [\"order.completed\", \"payment.failed\"]\n    String secret                   \u002F\u002F секрет для подписи\n) {}\n\n@Service\n@RequiredArgsConstructor\n@Slf4j\npublic class WebhookSender {\n\n    private final RestClient restClient;\n    private final WebhookRepository webhookRepository;\n\n    @Async\n    @TransactionalEventListener\n    public void onOrderCompleted(OrderCompletedEvent event) {\n        List\u003CWebhookRegistration> hooks = webhookRepository\n            .findByEvent(\"order.completed\");\n        for (WebhookRegistration hook : hooks) {\n            sendWebhook(hook, event);\n        }\n    }\n\n    private void sendWebhook(WebhookRegistration hook, Object payload) {\n        String body = objectMapper.writeValueAsString(payload);\n        String signature = HmacUtils.hmacSha256Hex(hook.getSecret(), body);\n\n        try {\n            restClient.post()\n                .uri(hook.getUrl())\n                .header(\"X-Webhook-Signature\", \"sha256=\" + signature)\n                .header(\"X-Webhook-Event\", \"order.completed\")\n                .body(body)\n                .retrieve()\n                .toBodilessEntity();\n        } catch (Exception e) {\n            log.error(\"Webhook delivery failed: {}\", hook.getUrl(), e);\n            webhookRetryService.scheduleRetry(hook, payload);\n        }\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n### Паттерн 3: SSE (Server-Sent Events)\n\nСервер отправляет поток событий клиенту через постоянное HTTP-соединение. Однонаправленный.\n\n\u003Cdetails>\u003Csummary>Реализация\u003C\u002Fsummary>\n\n```java\n@RestController\n@RequestMapping(\"\u002Fapi\u002Fevents\")\npublic class SseController {\n\n    private final SseEmitterService sseService;\n\n    @GetMapping(value = \"\u002Fstream\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\n    public SseEmitter streamEvents(@RequestParam Long userId) {\n        SseEmitter emitter = new SseEmitter(0L);\n        sseService.register(userId, emitter);\n\n        emitter.onCompletion(() -> sseService.unregister(userId));\n        emitter.onTimeout(() -> sseService.unregister(userId));\n\n        return emitter;\n    }\n}\n\n@Service\npublic class SseEmitterService {\n\n    private final Map\u003CLong, SseEmitter> emitters = new ConcurrentHashMap\u003C>();\n\n    public void sendEvent(Long userId, String eventType, Object data) {\n        SseEmitter emitter = emitters.get(userId);\n        if (emitter != null) {\n            try {\n                emitter.send(SseEmitter.event()\n                    .name(eventType)\n                    .data(data, MediaType.APPLICATION_JSON)\n                    .id(UUID.randomUUID().toString())\n                    .reconnectTime(3000));\n            } catch (IOException e) {\n                emitters.remove(userId);\n            }\n        }\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n### Паттерн 4: WebSocket (двунаправленный)\n\n\u003Cdetails>\u003Csummary>Реализация\u003C\u002Fsummary>\n\n```java\n@Configuration\n@EnableWebSocketMessageBroker\npublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer {\n\n    @Override\n    public void configureMessageBroker(MessageBrokerRegistry registry) {\n        registry.enableSimpleBroker(\"\u002Ftopic\", \"\u002Fqueue\");\n        registry.setApplicationDestinationPrefixes(\"\u002Fapp\");\n    }\n\n    @Override\n    public void registerStompEndpoints(StompEndpointRegistry registry) {\n        registry.addEndpoint(\"\u002Fws\")\n            .setAllowedOriginPatterns(\"*\")\n            .withSockJS();\n    }\n}\n\n@Controller\n@RequiredArgsConstructor\npublic class ChatController {\n\n    private final SimpMessagingTemplate messagingTemplate;\n\n    @MessageMapping(\"\u002Fchat.send\")\n    public void sendMessage(@Payload ChatMessage message,\n                            SimpMessageHeaderAccessor headerAccessor) {\n        String userId = headerAccessor.getUser().getName();\n        message.setSenderId(userId);\n        message.setTimestamp(Instant.now());\n\n        messagingTemplate.convertAndSend(\n            \"\u002Ftopic\u002Fchat.\" + message.getRoomId(), message);\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n### Частые ошибки\n\n- Использование WebSocket там, где достаточно SSE.\n- Отсутствие retry-механизма для webhook.\n- Polling без `Retry-After` заголовка.\n- Хранение состояния SSE\u002FWebSocket в памяти без учёта масштабирования.\n\n### Как используется в 2026\n\n- Spring WebFlux + SSE — реактивный подход для потоковых данных.\n- AsyncAPI 3.0 — стандарт описания асинхронных API.\n- Virtual threads (Java 21+) упрощают обработку большого числа SSE\u002FWebSocket-соединений.\n- Standard Webhooks (standardwebhooks.com) — единый формат подписи и retry.\n\n> **На собеседовании:** нужно знать все четыре паттерна (Polling, Webhooks, SSE, WebSocket) и когда использовать какой. Polling — самый простой для долгих операций, Webhooks — для интеграций, SSE — для уведомлений, WebSocket — только для полнодуплексной связи. Частая ошибка — предлагать WebSocket для всех async-задач.","","senior",[15],"rest",[],null,{"title":19,"description":20,"ogTitle":19,"ogDescription":21,"keywords":22,"schemaAnswer":23,"featuredSnippetReady":24},"Что такое асинхронные API и как их проектировать? — Gymterview","Асинхронные API — подходы к взаимодействию клиента и сервера, при которых обработка запроса не блокирует клиента и результат доставляется позже (через callback,","Асинхронные API — подходы к взаимодействию клиента и сервера, при которых обработка запроса не блокирует клиента и резул",[15,13],"Асинхронные API — подходы к взаимодействию клиента и сервера, при которых обработка запроса не блокирует клиента и результат доставляется позже (через callback, polling или push-уведомление).",true]