JasperFx#276 Phase 2: Adopt FEC-free projection apply-method dispatch#4475
Merged
Conversation
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.
This was referenced May 19, 2026
Closed
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>
This was referenced May 19, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 byJasperFx.Events.SourceGenerator— no runtime reflection overApply/Create/ShouldDeletemethods. This PR is the Marten-side migration to that path.References JasperFx/jasperfx#276 (Phase 1, FEC retirement, already merged). Also picks up:
DetermineActionAsyncdelete-branch tuple-wrapSnapshot<T>()self-aggregating dispatchtryUseAssemblyRegisteredEvolverhonorsIGeneratedSyncDetermineAction[Identity]attr, required-member init, ctor-sideDeleteEvent<T>, async self-aggregating)Pkg bumps
JasperFxJasperFx.EventsJasperFx.Events.SourceGeneratorMartenMigration done
partialso the SG can emit a sibling dispatcher.ProjectEvent<T>(...)/CreateEvent<T>(...)/DeleteEvent<T>(predicate)— are gone in the JasperFx 2.0 alphas).samples/EventSourcingIntro/Program.cs+ HelpdeskIncidents/*samples migrated to method conventions; affected doc snippets (docs/events/projections/*.md) re-rendered via mdsnippets.Bug_3665_compiling_with_private_membersdeleted — exercised the now-dropped private-Applypattern.aggregation_projection_validation_rulestests 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).InvoiceinScenarioAggregateAndRepositoryflipped topublicApply methods +publicparameterless ctor.JasperFx.Events.SourceGeneratordirectly as an analyzer (Marten.csprojkeepsPrivateAssets=\"all\"so the analyzer doesn't flow transitively):CoreTests.csproj,DocumentDbTests.csproj,MultiTenancyTests.csproj,ValueTypeTests.csproj. Mirrors the existing wiring onEventSourcingTests/DaemonTests.public— handlers must bepublic; pre-9.0 reflection picked upprivate/internal/protectedmembers. Includes the before/after rewrite for the documented Aggregate-as-Repository pattern and a callout that the SG falls back toRuntimeHelpers.GetUninitializedObject(typeof(T))when the parameterless ctor isn't public (field initializers do not run in that fallback).Idmembers — annotating with[Identity]is the supported way to override the Id-by-convention rule. The runtimeSchema.For<T>().Identity(x => ...)override isn't visible to the SG at compile time and can't be honored.new T { Required = default! }; Apply(event, s);in the null-snapshot branch.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-emittedIGeneratedSyncDetermineActionbatches events in one call, so per-event dead-letter routing underSkipApplyErrors=trueno longer fires. Tracked as jasperfx#303.Test plan
Full Marten test run against the published NuGets:
EventSourcingTestsDaemonTestsCoreTestsDocumentDbTestsMultiTenancyTestsPatchingTestsLinqTestsCompiledQueryTestsPre-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/AsyncProjectionShardmissing) 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 andAggregateStreamAsync<T>()call sites get an SG-emitted dispatcher.Commit shape
JasperFx#276 Phase 2: Adopt FEC-free source-generated projection dispatch— pkg bumps, ~80-file source migration, csproj analyzer wiring, deleted/skipped tests.docs: 9.0 migration entries for the new dispatch-pattern requirements—docs/migration-guide.mdonly.Bump Marten to 9.0.0-alpha.3—Directory.Build.props.🤖 Generated with Claude Code