Skip to content

fix: activate tripartite storage routing outside InitializeAsync - #42

Merged
chadahoochie merged 1 commit into
mainfrom
fix/projection-storage-routing
Aug 21, 2026
Merged

fix: activate tripartite storage routing outside InitializeAsync#42
chadahoochie merged 1 commit into
mainfrom
fix/projection-storage-routing

Conversation

@alexanetdev

Copy link
Copy Markdown
Collaborator

Storage routing is driven by StoreOptions.IsProjectionReadModel, whose registry was built only by Freeze(), which in turn was called only by DocumentStore.InitializeAsync. AddAquila never calls InitializeAsync, so in the documented DI setup the registry stayed empty and IsProjectionReadModel answered false for every type. Reads resolved to DocumentStorage while the projection writers targeted ProjectionStorage unconditionally, so read models were written to one store and read from another: LoadAsync returned null indefinitely, and SingleStreamProjection re-derived from an empty aggregate on every batch because its pre-fold load missed.

  • Freeze at the end of configuration rather than at the start of I/O: in the DocumentStore constructor, in the QuerySessionBase constructor (sessions can be built directly from StoreOptions), and in AddAquila right after configure, so the polyglot guard reports misconfiguration during startup.
  • Resolve every read and write through one StoreOptions.GetStorageFor(Type), so a read and a write of the same type cannot disagree about which store holds it.
  • IsProjectionReadModel now throws when queried before Freeze rather than answering false, so a configuration-ordering mistake cannot silently become a data-routing one.

Two adjacent defects had to be fixed to ship this, because freezing earlier makes the polyglot guard run in configurations where it previously never did:

  • The three storage roles defaulted to three distinct InMemoryStorageProvider instances, so a default StoreOptions read as polyglot and rejected the default Inline lifecycle, naming the same provider on both sides of the comparison. One shared instance now backs all three, and ProjectionStorage falls back to DocumentStorage when not explicitly configured -- a store is polyglot because the caller made it so, never by omission.
  • Polyglot detection compared provider references, so the segregated extensions (UseCosmosDocuments + UseCosmosEvents) produced distinct instances over one Cosmos account and read as polyglot. Detection now compares ProviderName.

Tests: two regression tests reproduce the original symptom (verified failing against the previous behaviour, LoadAsync returning null), plus coverage for the default-options case, same-backend distinct instances, unconfigured projection storage falling back to documents, and Freeze running during AddAquila.

Core 334 passed, Cosmos 175 non-integration passed, Redis 49 passed. Async_MultiStreamProjection_Processes_Actual_ChangeFeed_Documents_From_CosmosContainer fails in the full integration run and passes in isolation, identically before and after this change -- a pre-existing order-dependent flake, untouched here.

Storage routing is driven by StoreOptions.IsProjectionReadModel, whose registry
was built only by Freeze(), which in turn was called only by
DocumentStore.InitializeAsync. AddAquila never calls InitializeAsync, so in the
documented DI setup the registry stayed empty and IsProjectionReadModel answered
false for every type. Reads resolved to DocumentStorage while the projection
writers targeted ProjectionStorage unconditionally, so read models were written
to one store and read from another: LoadAsync returned null indefinitely, and
SingleStreamProjection re-derived from an empty aggregate on every batch because
its pre-fold load missed.

- Freeze at the end of configuration rather than at the start of I/O: in the
  DocumentStore constructor, in the QuerySessionBase constructor (sessions can be
  built directly from StoreOptions), and in AddAquila right after configure, so
  the polyglot guard reports misconfiguration during startup.
- Resolve every read and write through one StoreOptions.GetStorageFor(Type), so a
  read and a write of the same type cannot disagree about which store holds it.
- IsProjectionReadModel now throws when queried before Freeze rather than
  answering false, so a configuration-ordering mistake cannot silently become a
  data-routing one.

Two adjacent defects had to be fixed to ship this, because freezing earlier makes
the polyglot guard run in configurations where it previously never did:

- The three storage roles defaulted to three distinct InMemoryStorageProvider
  instances, so a default StoreOptions read as polyglot and rejected the default
  Inline lifecycle, naming the same provider on both sides of the comparison.
  One shared instance now backs all three, and ProjectionStorage falls back to
  DocumentStorage when not explicitly configured -- a store is polyglot because
  the caller made it so, never by omission.
- Polyglot detection compared provider references, so the segregated extensions
  (UseCosmosDocuments + UseCosmosEvents) produced distinct instances over one
  Cosmos account and read as polyglot. Detection now compares ProviderName.

Tests: two regression tests reproduce the original symptom (verified failing
against the previous behaviour, LoadAsync returning null), plus coverage for the
default-options case, same-backend distinct instances, unconfigured projection
storage falling back to documents, and Freeze running during AddAquila.

Core 334 passed, Cosmos 175 non-integration passed, Redis 49 passed.
Async_MultiStreamProjection_Processes_Actual_ChangeFeed_Documents_From_CosmosContainer
fails in the full integration run and passes in isolation, identically before and
after this change -- a pre-existing order-dependent flake, untouched here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chadahoochie

Copy link
Copy Markdown
Owner

Thanks man! This was the last thing I added and had not had a chance to verify yet.

@chadahoochie chadahoochie reopened this Aug 21, 2026
@chadahoochie
chadahoochie merged commit b90653d into main Aug 21, 2026
7 of 8 checks passed
@chadahoochie
chadahoochie deleted the fix/projection-storage-routing branch August 21, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants