Очереди, потоки, события
UX при eventual consistency: сделай асинхронный бэкенд мгновенным
Читать про consistency-окно — не то же самое, что спроектировать UI, который его переживёт. Построй небольшое приложение над бэкендом, который намеренно возвращает 202 и пишет ~700мс спустя, затем применяй каждый инструмент юнита, пока опыт не станет мгновенным и честным — с доказуемо обработанными failure mode.
Преврати ментальную модель юнита в рабочий фронтенд: предсказывай-и-сверяй там, где можешь, показывай честное pending-состояние там, где не можешь, ключуй retry и разрешай реальный конфликт читаемо — доказывая каждое поведение воспроизводимым тестом.
Построй приложение задач/заметок над намеренно асинхронным бэкендом (POST возвращает 202, consumer применяет запись ~700мс спустя, с инжектируемым уровнем сбоев), чтобы каждое действие пользователя ощущалось отзывчивым и ни один failure mode — ложный успех, бесконечный спиннер, double-submit, тихая перезапись, нарушение read-your-writes — не мог случиться.
- Демо (или тест), показывающее, что упавшая optimistic-запись чисто откатывается к состоянию до мутации, без осиротевшего предсказания на экране.
- Тест, доказывающий, что путь timeout срабатывает, когда consumer не подтверждает — pending-состояние разрешается в affordance 'всё ещё в обработке / повторить', а не крутится вечно.
- Тест, доказывающий, что два быстрых сабмита с одним idempotency key дают ровно один серверный эффект (одна строка, одно списание), а не два.
- Демо, показывающее, что read-your-own-writes держится: изменение пользователя остаётся видимым через consistency-окно, и итоговый refresh сверяется с ним, а не стирает его.
- Короткий разбор, связывающий каждое поведение UI с правилом юнита (predict vs pending, когда держишь предсказание vs сверенное значение, почему конфликт разрешён именно так).
- Замени reconciliation на polling push-каналом (WebSocket/SSE), сигнализирующим, когда consumer закончил, и покажи, как consistency-окно закрывается на событии, а не по таймеру.
- Добавь реальный CRDT (например, Yjs/Automerge) для коллаборативного текстового поля и покажи, как два клиента сходятся к одному документу без потерянных правок, затем сравни с версией на LWW.
- Добавь offline-очередь: записи, сделанные офлайн, эхаются локально, ставятся в очередь со своими idempotency key и сбрасываются по порядку при reconnect без дублирования эффектов.
- Добавь наблюдаемость: покажи живую задержку consistency-окна (accepted → readable) как метрику и построй её p50/p99, чтобы видеть зазор, который прячет твой UX.
Это цикл, который ты будешь запускать на каждой фиче над асинхронным бэкендом: реши на каждое действие, предсказуем ли результат (optimistic UI с rollback, которого требует контракт) или это дело сервера (честное pending-состояние под защитой timeout), ключуй retry, чтобы двойной клик не списал дважды, эхай записи локально, чтобы read-your-own-writes пережил consistency-окно, и разрешай конфликты читаемо вместо тихой перезаписи. Построив это раз на намеренно лагающем бэкенде, ты доводишь production-версию — где лаг реален и прерывист — до уровня мышечной памяти.