Skip to content

JasperFx#276 Phase 2: Adopt FEC-free projection apply-method dispatch#4475

Merged
jeremydmiller merged 4 commits into
masterfrom
feat/276-adopt-fec-free-dispatch
May 19, 2026
Merged

JasperFx#276 Phase 2: Adopt FEC-free projection apply-method dispatch#4475
jeremydmiller merged 4 commits into
masterfrom
feat/276-adopt-fec-free-dispatch

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Summary

Marten 9 picks up the JasperFx 2.0 alphas that retire FastExpressionCompiler-driven projection apply dispatch. Aggregation now flows exclusively through the source-generator-emitted [GeneratedEvolver] partial classes produced by JasperFx.Events.SourceGenerator — no runtime reflection over Apply / Create / ShouldDelete methods. This PR is the Marten-side migration to that path.

References JasperFx/jasperfx#276 (Phase 1, FEC retirement, already merged). Also picks up:

Pkg bumps

Pkg Before After
JasperFx 2.0.0-alpha.13 2.0.0-alpha.14
JasperFx.Events 2.0.0-alpha.12 2.0.0-alpha.13
JasperFx.Events.SourceGenerator 2.0.0-alpha.5 2.0.0-alpha.6
Marten 9.0.0-alpha.2 9.0.0-alpha.3

Migration done

  • ~80 projection-subclass test types made partial so the SG can emit a sibling dispatcher.
  • Two inline-lambda regression files deleted (the APIs they exercised — ProjectEvent<T>(...) / CreateEvent<T>(...) / DeleteEvent<T>(predicate) — are gone in the JasperFx 2.0 alphas).
  • samples/EventSourcingIntro/Program.cs + Helpdesk Incidents/* samples migrated to method conventions; affected doc snippets (docs/events/projections/*.md) re-rendered via mdsnippets.
  • Bug_3665_compiling_with_private_members deleted — exercised the now-dropped private-Apply pattern.
  • Three aggregation_projection_validation_rules tests removed — asserted on specific old-runtime error messages the SG no longer produces (the SG silently skips signatures it can't dispatch; covered in the migration guide).
  • Invoice in ScenarioAggregateAndRepository flipped to public Apply methods + public parameterless ctor.
  • Test projects that declare their own projection / aggregate types now reference JasperFx.Events.SourceGenerator directly as an analyzer (Marten.csproj keeps PrivateAssets=\"all\" so the analyzer doesn't flow transitively): CoreTests.csproj, DocumentDbTests.csproj, MultiTenancyTests.csproj, ValueTypeTests.csproj. Mirrors the existing wiring on EventSourcingTests / DaemonTests.
  • New migration-guide entries:
    • Aggregation method visibility now required to be public — handlers must be public; pre-9.0 reflection picked up private / internal / protected members. Includes the before/after rewrite for the documented Aggregate-as-Repository pattern and a callout that the SG falls back to RuntimeHelpers.GetUninitializedObject(typeof(T)) when the parameterless ctor isn't public (field initializers do not run in that fallback).
    • Identity-by-attribute on non-Id members — annotating with [Identity] is the supported way to override the Id-by-convention rule. The runtime Schema.For<T>().Identity(x => ...) override isn't visible to the SG at compile time and can't be honored.
    • Required-member aggregates — supported via new T { Required = default! }; Apply(event, s); in the null-snapshot branch.
    • Validation-rule behavior changes — covers the three retired runtime checks.

Documented limitations (skipped tests)

  • stream_aggregation.stream_id_is_set_as_string — runtime identity override invisible to the SG.
  • rebuilds_with_serialization_or_poison_pill_events.rebuild_the_projection_skip_failed_events — SG-emitted IGeneratedSyncDetermineAction batches events in one call, so per-event dead-letter routing under SkipApplyErrors=true no longer fires. Tracked as jasperfx#303.

Test plan

Full Marten test run against the published NuGets:

Project Pass / Fail / Skip
EventSourcingTests 1,320 / 0 / 7
DaemonTests 183 / 0 / 1
CoreTests 444 / 0 / 1
DocumentDbTests 987 / 0 / 1
MultiTenancyTests 128 / 0 / 0
PatchingTests 122 / 0 / 1
LinqTests 1,257 / 0 / 1
CompiledQueryTests 9 / 0 / 0

Pre-existing failures not introduced by this PR (also fail on master):

  • ValueTypeTests — 127 strong-typed-id failures, tracked in #4474, milestoned 9.0.

  • Marten.CommandLine.Tests — compile errors (IProjectionStore / ProjectionLifecycle / AsyncProjectionShard missing) inherited from a JasperFx rename. Out of scope here.

  • CI green across the matrix (.NET 9 / .NET 10 × Newtonsoft / System.Text.Json × Postgres 15 / latest).

  • One-click smoke against the new alphas in a downstream consumer project — confirm Snapshot<T>() registrations and AggregateStreamAsync<T>() call sites get an SG-emitted dispatcher.

Commit shape

  1. JasperFx#276 Phase 2: Adopt FEC-free source-generated projection dispatch — pkg bumps, ~80-file source migration, csproj analyzer wiring, deleted/skipped tests.
  2. docs: 9.0 migration entries for the new dispatch-pattern requirementsdocs/migration-guide.md only.
  3. Bump Marten to 9.0.0-alpha.3Directory.Build.props.

🤖 Generated with Claude Code

jeremydmiller and others added 4 commits May 18, 2026 19:47
JasperFx 2.0 retired the FastExpressionCompiler fallback inside
`JasperFx.Events.Aggregation` / `EventProjectionApplication`. Projection
dispatch in Marten 9 now flows exclusively through the SG-emitted
`[GeneratedEvolver]` partial classes produced by
`JasperFx.Events.SourceGenerator`. This commit picks up the new
JasperFx alphas and migrates Marten's test corpus + docs samples onto
the FEC-free path.

* `Directory.Packages.props`:
  - `JasperFx` 2.0.0-alpha.13 → 2.0.0-alpha.14
  - `JasperFx.Events` 2.0.0-alpha.12 → 2.0.0-alpha.13
  - `JasperFx.Events.SourceGenerator` 2.0.0-alpha.5 → 2.0.0-alpha.6

* Test / sample migration to the method-convention shape:
  - Every projection subclass exercised by the test suite is now
    `partial class` so the SG can emit the dispatcher beside it. ~80
    files touched across `src/EventSourcingTests`, `src/DaemonTests`,
    `src/CoreTests`, `src/DocumentDbTests`, `src/MultiTenancyTests`,
    `src/Marten.Testing`, `src/samples/**`.
  - The two pure inline-lambda regression files
    (`when_using_inline_lambdas_to_define_immutable_projection.cs`,
    `when_using_inline_lambdas_to_define_the_projection.cs`) are
    removed — their entire purpose was exercising the removed
    `ProjectEvent<T>(...)` / `CreateEvent<T>(...)` / `DeleteEvent<T>(predicate)`
    overloads.
  - Misc xUnit fixtures and aggregate records updated to satisfy the SG
    constraints carried into 9.0 (parents made `partial` where they
    host nested projections; collision-prone names made unique;
    private parameterless ctors changed to public where the test
    pattern needed it).
  - `samples/EventSourcingIntro/Program.cs` and the Helpdesk
    `Incidents/*` samples migrated to the method-convention shape so
    docs snippets resolve.
  - `docs/events/projections/*.md` re-rendered via mdsnippets after
    the underlying source moved.

* SG analyzer wiring on consumer test projects:
  - `Marten.csproj` keeps `PrivateAssets="all"` on the SG package, so
    the analyzer doesn't flow transitively. Test projects that declare
    their own projection / aggregate types now reference the SG
    package directly as an analyzer:
    `CoreTests.csproj`, `DocumentDbTests.csproj`,
    `MultiTenancyTests.csproj`, `ValueTypeTests.csproj`. Mirrors the
    existing wiring on `EventSourcingTests` / `DaemonTests`.

* Validation-rule tests retired:
  - `aggregation_projection_validation_rules.blow_on_soft_deleted_aggregates`,
    `find_bad_method_names_that_are_not_ignored`, `missing_required_parameter`
    asserted on specific old-runtime error messages that no longer fire —
    the SG silently skips signatures it can't dispatch. Removed with
    inline comments pointing at the new behavior; covered in the
    migration guide entry [Aggregation method visibility now required
    to be public].
  - `Bug_3665_compiling_with_private_members` deleted — explicitly
    tested the private-member dispatch pattern which 9.0 drops.

* Documented limitations skipped:
  - `stream_aggregation.stream_id_is_set_as_string` skipped — uses
    `opts.Schema.For<T>().Identity(x => x.Key)` runtime override which
    the source generator can't see at compile time.
    Annotate the member with `[Identity]` instead in 9.0.
  - `rebuilds_with_serialization_or_poison_pill_events.rebuild_the_projection_skip_failed_events`
    skipped pending JasperFx/jasperfx#303 — SG-emitted
    `IGeneratedSyncDetermineAction` batches events in a single call,
    so per-event dead-letter routing under `SkipApplyErrors=true` no
    longer fires.

Validation: full Marten test run against the published NuGets:
| Project | Pass / Fail / Skip |
|---|---|
| EventSourcingTests | 1,320 / 0 / 7 |
| DaemonTests | 183 / 0 / 1 |
| CoreTests | 444 / 0 / 1 |
| DocumentDbTests | 987 / 0 / 1 |
| MultiTenancyTests | 128 / 0 / 0 |
| PatchingTests | 122 / 0 / 1 |
| LinqTests | 1,257 / 0 / 1 |
| CompiledQueryTests | 9 / 0 / 0 |

Pre-existing failures not introduced by this work:
- `ValueTypeTests` — 127 strong-typed-id failures
  (tracked in marten#4474, milestoned 9.0)
- `Marten.CommandLine.Tests` — compile errors on master too
  (`IProjectionStore` / `ProjectionLifecycle` / `AsyncProjectionShard`
   missing after a JasperFx rename)

Refs: JasperFx/jasperfx#276 (Phase 1 — FEC retirement), #290, #292,
#293, #295, #297, #298, #303.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Aggregation method visibility now required to be `public` — pre-9.0
  reflection picked up private / internal / protected handlers; the
  SG-emitted dispatcher can only invoke `public` members of the user's
  type. Migration table covers private `Apply` / `ShouldDelete`, the
  event-shaped ctor case, and the `GetUninitializedObject` fallback
  the SG uses when the parameterless ctor is non-public (field
  initializers do not run in that fallback — call out for users who
  rely on them).
* Identity-by-attribute on non-`Id` members — Marten 9 respects
  `[Identity]` at compile time. Documents the workaround for
  aggregates configured via the runtime `Schema.For<T>().Identity(x => ...)`
  override (annotate the member, or rename to `Id`).
* Required-member aggregates — the SG emits
  `new T { Required = default! }` for the null-snapshot branch and
  immediately runs Apply; users who need explicit construction can
  add a `static T Create(SomeEvent e)` method instead.
* Validation-rule behavior changes — old-runtime
  `InvalidProjectionException` messages around unrecognized method
  names, missing aggregate parameters, and soft-delete + conventional-
  method conflicts are no longer thrown; the SG silently skips
  signatures it can't dispatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cuts the alpha that consumes JasperFx 2.0.0-alpha.14 /
JasperFx.Events 2.0.0-alpha.13 / JasperFx.Events.SourceGenerator
2.0.0-alpha.6 and the FEC-free source-generated projection dispatch
landed in this PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The two bullet lists I added under the new dispatch-pattern sections used
`-` bullets; the repo's markdownlint config expects `*`. Swap to `*` to
clear the 7 MD004 errors on the docs-prs workflow.
@jeremydmiller jeremydmiller merged commit f087531 into master May 19, 2026
6 checks passed
@jeremydmiller jeremydmiller deleted the feat/276-adopt-fec-free-dispatch branch May 19, 2026 01:05
jeremydmiller added a commit that referenced this pull request May 19, 2026
JasperFx PR #285 ("Stop daemon-internal cancellations from leaking
into CatchUpAsync") shipped in JasperFx.Events 2.0.0-alpha.11 and
is consumed by Marten's current alpha.13 pin (#4475 Phase 2). The
daemon-internal cancellation flake that motivated the re-skip in
#4462/#4463 is gone — confirmed locally 5/5 on both net9.0 and
net10.0.

Test-only change. Closes #4466.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller added a commit that referenced this pull request May 19, 2026
Phase 2 (PR #4475) made JasperFx.Events.SourceGenerator the only
projection-apply path. This audit walks every projection-shaped
type across src/EventSourcingTests, src/DaemonTests, src/CoreTests,
src/DocumentDbTests, src/MultiTenancyTests, src/PatchingTests,
src/samples, and src/DocSamples, and inventories the SG dispatch
shape per type.

Two SG emission patterns surfaced from a representative dump
(-p:EmitCompilerGeneratedFiles=true on EventSourcingTests):

  Pattern A — projection subclass: SG emits a partial declaration
  that merges into the user's class. Source must be partial. This
  is the source of Phase 2's ~80 partial annotations on test-library
  projection subclasses.

  Pattern B — self-aggregating aggregate: SG emits a separate sibling
  `XEvolver` class plus an [assembly: GeneratedEvolver(typeof(X),
  typeof(XEvolver))] registration. Aggregate type does NOT need
  partial — the evolver is an independent type calling the
  aggregate's public methods.

Headline counts:

  221 projection-subclass types (Pattern-A emit)
  - 189 partial (SG-merged dispatcher expected)
  - 30 non-partial with no Apply/Create (no SG emission expected)
  - 2 non-partial implementing IProjection.ApplyAsync directly
    (SignalRProducer / KafkaProducer; low-level handlers, no SG
    emission expected)
  - 29 explicit Evolve/EvolveAsync override (deliberate SG bypass)
  - 8 explicit DetermineAction/DetermineActionAsync override
    (deliberate SG bypass)

  18 self-aggregating aggregate types (Pattern-B emit)

  Total dispatcher emission sites: 239
  SG gaps surfaced: 0

Inventory landed under audit/projections-inventory.md. No code
changes; this commit is documentation-only. Follow-up: extend
Marten.AotSmoke with an automated dispatcher-coverage assertion so
future SG regressions trip CI immediately (tracked separately).

Refs #4471.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant