Skip to content

Inject read models from non-Chronicle providers into the command pipeline - #2423

Closed
woksin wants to merge 7 commits into
mainfrom
claude/issue-2303-nonchronicle-readmodels
Closed

Inject read models from non-Chronicle providers into the command pipeline#2423
woksin wants to merge 7 commits into
mainfrom
claude/issue-2303-nonchronicle-readmodels

Conversation

@woksin

@woksin woksin commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the design proposed in #2303 so an EF Core-backed [ReadModel] can be injected by key into a command, not just Chronicle-backed ones.

Reviewer notes — this was authored without a local .NET SDK, so it's verified by inspection + CI only, and it touches Chronicle's command-context plumbing, so a couple of decisions are worth your eyes:

  • Provider-neutral key (the crux from Support injecting read models from non-Chronicle providers into the command pipeline #2303): the resolved key is lifted into Arc.Core as Cratis.Arc.Commands.CommandContextKeys.ResolvedKey (a plain string); Chronicle's EventSourceValuesProvider now writes it alongside its existing EventSourceId, and EF reads only this Arc-level string. Chronicle behavior is otherwise unchanged.
  • Two deviations from the prose proposal: (1) resolvers are handed to AddReadModelsForCommand at registration rather than discovered via IInstancesOf<> — registering a scoped by-key factory per read model type needs the type list at registration, which each provider already computes; the shared abstraction + additive union are preserved. (2) The key holder is CommandContextKeys (not WellKnownCommandContextKeys) to avoid a CS0104 clash with Chronicle's identically-named type.
  • Known boundary: only Chronicle currently writes the neutral key, so a pure EF-without-Chronicle app wouldn't have it populated. The Support injecting read models from non-Chronicle providers into the command pipeline #2303 coexistence scenario always includes Chronicle; a dedicated writer can be added if pure-EF is a target.
  • Points I'd most want CI/your review to confirm: DbContext.Find on a read-only/no-tracking context, the ConceptAs<T> primary-key conversion path, and the SQLite in-memory spec lifetime.

Added

  • Read models backed by non-Chronicle providers (such as Entity Framework Core) can be injected by key into a command's Handle(), Provide(), and CommandValidator<>, through a provider-agnostic resolver abstraction; Chronicle and EF-backed read models coexist without either claiming the other's types, and nullable/non-nullable resolution behaves exactly as before (Support injecting read models from non-Chronicle providers into the command pipeline #2303)

Generated by Claude Code

claude added 5 commits July 29, 2026 14:11
Introduce ICanResolveReadModelForCommand so any backing provider can
contribute a command-scoped, by-key read model resolver, and lift the
read model injection support out of Chronicle into Arc.Core:

- ICanResolveReadModelForCommand abstraction and AddReadModelsForCommand
  registration that registers a scoped by-key factory per read model
  type and keeps an additive union of registered types across providers.
- A provider-neutral resolved key on the command context (CommandContextKeys
  plus a GetResolvedKey accessor).
- RegisteredReadModelTypes, the unresolvable-dependency classifier, and the
  ReadModelDoesNotExistForCommand / UnableToResolveReadModelFromCommandContext
  validation failures, now keyed off the neutral resolved key.
Chronicle now contributes its read model types through the Arc.Core
ICanResolveReadModelForCommand seam instead of owning the registration
and classification itself:

- ChronicleReadModelForCommandResolver wraps the existing by-key
  resolution through IReadModels; AddReadModels registers it via
  AddReadModelsForCommand. No change to how Chronicle resolves or
  releases read models.
- EventSourceValuesProvider also writes the provider-neutral resolved
  key alongside the event source id, so non-Chronicle providers can
  resolve by the same key. Existing event-source-id behavior is unchanged.
- The moved RegisteredReadModelTypes, classifier, and validation failures
  are consumed from Arc.Core.
Add EntityFrameworkReadModelForCommandResolver, which resolves a
[ReadModel] entity from its owning ReadOnlyDbContext by the command's
resolved key. It discovers the read model entity -> DbContext map from
the registered read model contexts, converts the resolved key string to
the entity's primary key type (Guid, int, long, string, or a ConceptAs
of those), and loads the instance with DbContext.Find, returning null
when it does not exist. The resolver is registered automatically when
read model DbContexts are added.
- Arc.Core: registration behavior (scoped factory + additive type union
  across two providers), by-key resolution present/absent, and the
  unresolvable-dependency classifier keyed off the neutral resolved key.
- EF Core (SQLite in-memory): read model type discovery, by-key resolve
  present/absent, missing-key validation failure, and injection of an EF
  [ReadModel] through the command DI resolution seam.
Explain that EF Core [ReadModel] entities are injectable into a command
validator, Provide(), or Handle() by key, with the same nullable rules,
and that Chronicle-backed and EF-backed read models coexist.
@woksin woksin added the minor label Jul 29, 2026 — with Claude
claude and others added 2 commits July 29, 2026 14:21
- Await DbContext.FindAsync instead of the blocking Find (CA1849/MA0042)
- Add missing param docs to the test resolver context (RCS1141)
@woksin

woksin commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Consolidated into #2425 so the open work ships as one release. Every commit from this branch is contained in combined/one-release — nothing is lost, and the branch is left in place.

@woksin woksin closed this Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants