Показывает: как POST /api/v1/protocol/merge/byAgenda/{id} — sync-endpoint, unчей главный потребитель agenda-state-machine — реализуется поверх task-based инфраструктуры.
Актуальные ADR: ADR-0010 (3-фазная миграция), ADR-0009.
sequenceDiagram
autonumber
participant SM as agenda-state-machine<br/>GenerateProtocolByQuestionAction<br/>⟶ external (OBESRABUO_agenda)
participant Ctl as ProtocolLegacyController<br/>(protocol.api.legacy)
participant Svc as ProtocolGenerationTaskService<br/>(protocol.service)
participant Exec as protocolTaskExecutor<br/>(virtual threads)
participant Gen as ProtocolGenerator<br/>(protocol.service)
participant DB as protocol_generation_task<br/>(DB)
Note over SM: Триггер: все вопросы повестки<br/>получили REPORT_SIGNED/PROTECTED.<br/>State-machine вызывает sync v1 через Feign.
SM->>Ctl: POST /api/v1/protocol/merge/byAgenda/{id}<br/>(Feign client в agenda)
Note over Ctl: log.warn("Sync v1 called by {caller},<br/>migrate to task-based")<br/>— deprecation telemetry
%% ── Submit task внутренне ──
Ctl->>Svc: submit(agendaId)
Svc->>DB: save(task: PENDING)
Svc->>Exec: execute(() -> runGeneration(taskId, agendaId))
Svc-->>Ctl: taskId
Note over Ctl: Controller НЕ возвращает 202.<br/>Вместо этого — blocking poll-loop<br/>в том же HTTP thread.
%% ── Background generation (параллельно poll'у) ──
rect rgb(235, 245, 255)
Note over Exec,Gen: Background (virtual thread)
Exec->>Svc: runGeneration(taskId, agendaId)
Svc->>DB: status = IN_PROGRESS
Svc->>Gen: generate(agendaId)
Note over Gen: 4-phase pipeline (5-10 минут)
alt success
Gen-->>Svc: protocolId
Svc->>DB: status = COMPLETED, protocolId
else failure
Gen--xSvc: throws
Svc->>DB: status = FAILED, errorMessage
end
end
%% ── Blocking poll-loop в HTTP thread ──
rect rgb(255, 250, 235)
Note over Ctl,DB: Poll-loop с timeout 12 минут (configurable)
loop каждые 2 сек
Ctl->>Svc: getTask(taskId)
Svc->>DB: findById(taskId)
DB-->>Svc: task
Svc-->>Ctl: task
alt task.status == COMPLETED
Note over Ctl: выход из loop — success
else task.status == FAILED
Note over Ctl: выход из loop — failure
else task.status IN_PROGRESS
Note over Ctl: Thread.sleep(2s) и продолжаем
end
end
end
%% ── Terminal ──
alt success — protocol готов
Ctl->>Svc: crud.getProtocol(task.protocolId)
Svc-->>Ctl: ProtocolResponse
Ctl-->>SM: 200 OK ProtocolResponse
Note over SM: state transition:<br/>REPORT_SIGNED → PROTECTED
else failure — generation failed
Note over Ctl: выбрасываем DomainException.<br/>из task.errorMessage<br/>(wrap через парсер если надо)
Ctl--xSM: 4xx/5xx ProblemDetail<br/>(через GlobalExceptionHandler)
Note over SM: state-machine обработает fail,<br/>возможно retry
else timeout 12 min
Note over Ctl: Ctl бросает GatewayTimeout
Ctl--xSM: 504 Gateway Timeout<br/>ProblemDetail + taskId в теле<br/>«продолжи polling через /task/{id}/status»
Note over SM: state-machine может:<br/>— периодически проверять /task/{id}/status<br/>— считать что fail<br/>(бизнес-решение)
end
agenda-state-machine (в реф-коде GenerateProtocolByQuestionAction) ожидает синхронный Feign-вызов. Sync v1 endpoint держит HTTP-thread до готовности протокола.
На 10-минутных операциях это структурно плохо:
- Connection/read timeout — надо расширять на agenda-стороне до 15 мин.
- Thread-pool starvation — параллельные заседания забьют пул.
- Retry невозможен — если 8-я минута упала, agenda узнаёт только через 10 мин.
Но ломать agenda одномоментно нельзя → bridge работает в двух режимах:
- Внутренне — task submission + poll-loop в том же HTTP-thread (эмулирует sync снаружи).
- Внешне — agenda думает что это старый добрый sync call.
- M1 (текущий ship 2.0): bridge работает, agenda не меняется.
- M2 (следующий инкремент agenda): agenda-state-machine переводится на event-driven:
- Triger → POST /task → получает taskId → сохраняет в state.
- Подписка на
ObesrabuoProtocolGenerationCompleted через common-events → state-transition.
- Fallback: scheduled polling GET /status если events не подходят.
- M3 (protocol 3.0): sync v1 +
ProtocolLegacyController удаляются.
На 20% больше max generation time (предполагаемый 10 мин). Если упёрлись в timeout — не продолжаем ждать, возвращаем 504 с taskId. Caller может:
- Считать что generation провалилась.
- Самостоятельно опрашивать
/task/{taskId}/status — задача реально работает в фоне, результат будет.
Каждый вызов sync v1 логируется log.warn(...). Метрика для трекинга миграции: когда counter доходит до нуля → sync v1 можно удалять.
Даже если sync v1 вызвали, task всё равно создаётся в БД. Если что-то пошло не так — taskId остаётся, можно debug через GET /task/{id}/status после истории.