Files
slm-design/DRAFT/level-2/domains/state-cache.md
2026-08-02 22:53:05 +03:00

9.1 KiB
Raw Blame History

Состояние, cache и hydration

Пояснение границы между семантической властью Domain API и framework-owned materialization.

Связанные правила

Основная граница

Модуль api определяет форму и семантику доменных значений, но не выбирает способ их хранения и реактивной доставки. TanStack Query, SWR, Apollo, RTK Query, Zustand, Redux, MobX, Pinia, Signals и RxJS остаются в framework bindings или compositions.

Domain API
  → public model/outcome/event
  → framework projection
  → rendering

Concrete state/query runtime не импортируется модулем api, не является dependency port фабрики и не входит в публичный Domain API.

Виды materialization

Source cache

Технический cache внешнего provider внутри adapter. Он может отвечать за transport deduplication, connection state, provider retry и хранение port records.

Source cache не публикует raw DTO, query keys, mutable client или library result через Domain API. Если adapter создаёт timers, subscriptions или connection, он остаётся единственным владельцем и экспортирует lifecycle handle, который assembly только агрегирует. Если resource создаёт assembly, adapter использует его как borrowed capability и не закрывает самостоятельно.

Framework projection

State или cache, который framework binding строит из готового Domain API:

export const useAuthSession = () => {
  const api = useAuthApi()

  return useQuery({
    queryKey: ['auth', 'session'],
    queryFn: api.getSession,
  })
}

Query key, stale time, pending/retry status, Suspense, rendering stale data и техническая invalidation принадлежат binding. AuthSession и AuthError принадлежат api.

Composition state

Состояние конкретной страницы или multi-domain flow принадлежит composition: выбранная вкладка, открытый modal, draft формы, route transition и координация нескольких API.

Если draft приобретает самостоятельную доменную семантику, Domain API предоставляет validation или transition, но framework по-прежнему хранит возвращаемое readonly value.

Domain API не является store

Публичный Domain API не экспортирует:

  • mutable store;
  • getState и setState framework runtime;
  • QueryClient;
  • Zustand StoreApi;
  • framework hook;
  • глобальный singleton данных;
  • универсальный state port.

API methods возвращают значения и outcomes. Framework consumer решает, как долго их хранить и когда повторно запросить.

Это не означает, что framework определяет предметные transitions. Он материализует только то, что произвёл или проверил API.

Invalidation и retry

Политика Обычный владелец
Query key, stale time, deduplication, background refetch Framework binding
Transport retry безопасного запроса Adapter
Rendering stale data, Suspense, polling UI Framework binding или composition
Запрет повторной предметной команды Domain API
Cooldown, лимит попыток, допустимый transition Domain API
Freshness, влияющая на корректность сценария Domain API через operation contract

После успешной команды binding может технически invalidировать известные query keys. Если выбор invalidation выражает предметную семантику, Domain API возвращает устойчивый outcome/event, а binding только отображает его на framework cache.

Optimistic updates

Framework binding не конструирует произвольную публичную модель из form input, raw DTO или текущего cache. Optimistic projection допустима, когда предполагаемое значение:

  • возвращено командой Domain API;
  • создано отдельной операцией Domain API;
  • создано или проверено pure-функцией api/runtime.
import {
  projectProfileUpdate,
} from '@/domains/user/api/runtime'

const optimisticProfile = projectProfileUpdate(
  currentProfile,
  command,
)

queryClient.setQueryData(profileKey, optimisticProfile)

projectProfileUpdate владеет предметным transition, а setQueryData остаётся framework operation.

Concurrent mutations и realtime

При нескольких optimistic commands и realtime events binding не выбирает самостоятельно ordering, versioning, rollback или rebase. Domain API возвращает correlation/version metadata либо предоставляет deterministic reconciliation:

const nextProjection = reconcileProfile({
  current,
  event,
  pendingCommands,
})

Если API не объявляет безопасный merge, binding invalidates cache и получает authoritative snapshot через Domain API. Это предпочтительнее скрытого применения неполного delta.

Persistence

Framework cache может технически сохраняться между reloads, но persisted value не становится источником предметной истины. После восстановления значение:

  • используется как stale projection до revalidation;
  • либо проверяется публичным validator api/runtime;
  • либо отбрасывается и загружается через Domain API.

Если storage является самостоятельным предметным внешним источником, доступ к нему оформляется dependency port и adapter. Автоматический framework middleware не обходит API validation и transitions.

SSR и hydration

Server и client имеют разные API instances и caches:

server request
  → request assembly
  → server Domain API
  → server framework cache
  → serializable hydration payload

browser
  → client assembly
  → client Domain API
  → hydrated client cache

Hydration payload принадлежит framework binding и содержит только public domain values и framework metadata. API object, functions, ports, adapters, mutable clients и request secrets не сериализуются.

Server cache создаётся на каждый request и не хранится в module singleton. Client cache создаётся на согласованный application или route scope.

RSC и Server Actions

Server Component вызывает server Domain API и передаёт Client Component только сериализуемые values или hydration payload. Client Component создаёт или получает отдельный client API instance; при SSR его render отдельно проверяется в server prerender graph до browser hydration.

Server Action создаёт request-scoped production graph на каждый вызов, выполняет Domain API command и гарантированно выполняет все cleanup obligations графа. Client invocation Server Action является framework reference edge, а не передачей server API в browser.

Проверка на ревью

Для каждого state/query runtime определяется:

  • является ли он source cache, framework projection или composition state;
  • откуда поступают public domain values;
  • кто определяет validation и transition;
  • где находятся library-specific types и keys;
  • как invalidation связана с Domain API outcomes;
  • как обрабатываются optimistic concurrency и realtime events;
  • что сериализуется при SSR/RSC;
  • соответствует ли cache scope области жизни API graph.