Skip to content

Make IEventSerializer AOT-compatible - #524

Merged
alexeyzimarev merged 10 commits into
devfrom
feature/aot-serializer-cleanup
Jul 30, 2026
Merged

Make IEventSerializer AOT-compatible#524
alexeyzimarev merged 10 commits into
devfrom
feature/aot-serializer-cleanup

Conversation

@alexeyzimarev

@alexeyzimarevalexeyzimarev commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Remove [RequiresUnreferencedCode] and [RequiresDynamicCode] from IEventSerializer interface and all 43 consumer files across core and integration packages
  • Extract reflection-based DefaultEventSerializer to new Eventuous.Serialization.Json.Dynamic package
  • Annotate the DefaultEventSerializerconstructor with RequiresUnreferencedCode/RequiresDynamicCode (the same pattern System.Text.Json uses for DefaultJsonTypeInfoResolver): the IEventSerializer interface and all its call sites stay warning-free, while apps that opt into the reflection serializer get exactly one IL2026/IL3050 warning at the construction site
  • Add EventSerializer static holder class replacing DefaultEventSerializer.Instance / SetDefaultSerializer()
  • DefaultStaticEventSerializer and the core serialization interface are fully AOT-clean with zero suppression attributes; both serialization packages set IsAotCompatible=true so the trim/AOT analyzers verify the annotations at build time

Breaking changes

  • DefaultEventSerializer moved from Eventuous.Serialization to Eventuous.Serialization.Json.Dynamic package
  • DefaultEventSerializer.Instance replaced by EventSerializer.Default
  • DefaultEventSerializer.SetDefaultSerializer() replaced by EventSerializer.SetDefault()
  • Apps must explicitly configure a serializer (either DefaultStaticEventSerializer for AOT or DefaultEventSerializer from the new package)
  • Constructing DefaultEventSerializer in a trimmed/AOT-published app now produces IL2026/IL3050 build warnings at the construction site (intended behavior)

Test plan

  • Full solution builds with zero errors and zero IL trim/AOT warnings (from this change)
  • Trim analyzer verified active on the Dynamic package (removing a suppression produces IL2026)
  • Core tests pass (26/26)
  • Application tests pass (21/21)
  • Subscription tests pass (30/30)
  • DI extension tests pass (3/3)
  • Integration tests with infrastructure (require docker services)

🤖 Generated with Claude Code

Related

Remove [RequiresUnreferencedCode] and [RequiresDynamicCode] from the
IEventSerializer interface and all 43 consumer files across the codebase.
Extract the reflection-based DefaultEventSerializer to a new
Eventuous.Serialization.Json package. The core serialization interface
and DefaultStaticEventSerializer are now fully AOT-clean.
- Clean IEventSerializer interface (no AOT attributes)
- Add EventSerializer static holder replacing DefaultEventSerializer.Instance
- Move DefaultEventSerializer to Eventuous.Serialization.Json package
- Remove AOT attributes from Persistence, Producers, Subscriptions,
Application, and all integration packages (KurrentDB, Sql.Base,
RabbitMQ, Sqlite)
- Delete 6 Constants.cs files with DynamicSerializationMessage
- Update all samples and tests to use new API
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projectsBot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0)📘 Rule violations (0)📎 Requirement gaps (0)🎨 UX issues (0)🔗 Cross-repo conflicts (0)📜 Skill insights (0)

Grey Divider


Action required

1. Ctor overwrites default✓ Resolved🐞 Bug≡ Correctness
Description
DefaultEventSerializer unconditionally calls EventSerializer.SetDefault(this) in its
constructor, so creating an instance for local/DI use silently overwrites any previously configured
global serializer. This contradicts the comment (“if none is set”) and can cause unrelated
components to start serializing/deserializing with a different configuration.
Code

src/Core/src/Eventuous.Serialization.Json/DefaultEventSerializer.cs[R14-20]

+ public DefaultEventSerializer(JsonSerializerOptions options, ITypeMapper? typeMapper = null) {+ _options = options;+ _typeMapper = typeMapper ?? TypeMap.Instance;- readonly ITypeMapper _typeMapper = typeMapper ?? TypeMap.Instance;-- public static void SetDefaultSerializer(IEventSerializer serializer) => Instance = serializer;+ // Auto-register as default if none is set+ EventSerializer.SetDefault(this);+ }
Evidence
The DefaultEventSerializer constructor always sets the global default via
EventSerializer.SetDefault(this) (no guard), and EventSerializer.SetDefault is a plain
assignment to the static backing field. This makes every new DefaultEventSerializer(...) a global
mutation, even when used only as a local/DI-registered serializer instance.

src/Core/src/Eventuous.Serialization.Json/DefaultEventSerializer.cs[14-20]
src/Core/src/Eventuous.Serialization/EventSerializer.cs[12-28]
src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/StoreFixtureBase.cs[28-33]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
`DefaultEventSerializer` currently overwrites the process-wide default serializer every time it is constructed, which makes local/DI usage mutate global state and can break previously configured serializer behavior.
## Issue Context
The constructor comment says it should only auto-register &amp;quot;if none is set&amp;quot;, but the implementation calls `EventSerializer.SetDefault(this)` unconditionally. `EventSerializer.SetDefault` is a simple assignment.
## Fix Focus Areas
- src/Core/src/Eventuous.Serialization.Json/DefaultEventSerializer.cs[14-20]
- src/Core/src/Eventuous.Serialization/EventSerializer.cs[12-28]
## Implementation notes
- Add `public static bool TrySetDefault(IEventSerializer serializer)` in `EventSerializer` that sets the default only when currently null (e.g., via `Interlocked.CompareExchange`).
- Update `DefaultEventSerializer` ctor to call `TrySetDefault(this)` (or remove ctor side-effect entirely and require explicit `EventSerializer.SetDefault(...)`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unconfigured default in tests✓ Resolved🐞 Bug≡ Correctness
Description
Several test projects now call EventSerializer.Default without configuring a default serializer in
that test assembly, so the first access will throw InvalidOperationException. This breaks tests
like Azure ServiceBus ConvertEventToMessage, Kafka BasicProducerTests, and KurrentDB
CustomDependenciesTests.
Code

src/Azure/test/Eventuous.Tests.Azure.ServiceBus/ConvertEventToMessage.cs[R11-13]

 var builder = new ServiceBusMessageBuilder(
- DefaultEventSerializer.Instance,+ EventSerializer.Default,
"test-stream",
Evidence
EventSerializer.Default throws when _default is null. Multiple tests call
EventSerializer.Default directly, and their test projects do not reference the
Eventuous.Tests.Subscriptions assembly that contains the new [ModuleInitializer] setting a
default serializer, so those calls will throw at runtime.

src/Core/src/Eventuous.Serialization/EventSerializer.cs[18-22]
src/Azure/test/Eventuous.Tests.Azure.ServiceBus/ConvertEventToMessage.cs[10-14]
src/Kafka/test/Eventuous.Tests.Kafka/BasicProducerTests.cs[68-75]
src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/CustomDependenciesTests.cs[107-111]
src/Core/test/Eventuous.Tests.Subscriptions/TestSetup.cs[6-10]
src/Azure/test/Eventuous.Tests.Azure.ServiceBus/Eventuous.Tests.Azure.ServiceBus.csproj[3-6]
src/Kafka/test/Eventuous.Tests.Kafka/Eventuous.Tests.Kafka.csproj[8-11]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
Some test projects call `EventSerializer.Default` without configuring a default serializer in that test process, so tests throw `InvalidOperationException` immediately.
## Issue Context
Only `Eventuous.Tests.Subscriptions` adds a module initializer to construct `DefaultEventSerializer`, but Azure/Kafka/KurrentDB test projects reference `Eventuous.Tests.Subscriptions.Base` instead, so they do not get this initializer.
## Fix Focus Areas
- src/Azure/test/Eventuous.Tests.Azure.ServiceBus/ConvertEventToMessage.cs[10-14]
- src/Kafka/test/Eventuous.Tests.Kafka/BasicProducerTests.cs[68-75]
- src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/CustomDependenciesTests.cs[107-111]
- src/Core/test/Eventuous.Tests.Subscriptions.Base/Eventuous.Tests.Subscriptions.Base.csproj[1-20]
## Implementation notes
- Add a new `TestSetup.cs` with `[ModuleInitializer]` to `Eventuous.Tests.Subscriptions.Base` (since it is referenced by Azure/Kafka/KurrentDB tests) that calls `EventSerializer.SetDefault(new DefaultEventSerializer(new JsonSerializerOptions(JsonSerializerDefaults.Web)))`.
- Add a `ProjectReference` from `Eventuous.Tests.Subscriptions.Base` to `$(CoreRoot)\Eventuous.Serialization.Json\Eventuous.Serialization.Json.csproj` so the initializer can use `DefaultEventSerializer`.
- Alternatively, add equivalent initializers directly to each affected test project if you want to avoid pulling Json serializer into the base project.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:19ac3f8952

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


public static void SetDefaultSerializer(IEventSerializer serializer) => Instance = serializer;
// Auto-register as default if none is set
EventSerializer.SetDefault(this);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid resetting global serializer in constructor

Calling EventSerializer.SetDefault(this) inside DefaultEventSerializer's constructor makes every new instance mutate global process state, which is a behavioral regression from the previous implementation where construction was side-effect free unless callers explicitly set the default. This can break apps that create multiple serializers (for different ITypeMapper/options scopes) because the last constructed instance silently changes what all ... ?? EventSerializer.Default call sites use; at minimum this should only set the default when none is configured, as the comment indicates.

Useful? React with 👍 / 👎.

@github-actions

github-actionsBot commented Mar 13, 2026

Copy link
Copy Markdown

Test Results

 46 files + 24 46 suites +24 13m 20s ⏱️ -25s
369 tests + 4 369 ✅ + 4 0 💤 ±0 0 ❌ ±0 
688 runs +312 688 ✅ +312 0 💤 ±0 0 ❌ ±0 

Results for commit 0d87adb. ± Comparison against base commit 465263e.

This pull request removes 5 and adds 9 tests. Note that renamed tests count towards both.
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(07/29/2026 16:52:39 +00:00)
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(07/29/2026 16:52:39)
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(0a6f8f45-54a4-495d-8d22-36033b2b0e7d)
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-07-29T16:55:46.5456498+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-29T16:55:46.5456498+00:00 }, CommitPosition { Position: 0, Sequence: 4, Timestamp: 2026-07-29T16:55:46.5456498+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-07-29T16:55:46.5456498+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-29T16:55:46.5456498+00:00 })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-07-29T16:55:46.5456498+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-29T16:55:46.5456498+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-07-29T16:55:46.5456498+00:00 }, CommitPosition { Position: 0, Sequence: 8, Timestamp: 2026-07-29T16:55:46.5456498+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-29T16:55:46.5456498+00:00 })
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(07/30/2026 15:52:17 +00:00)
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(07/30/2026 15:52:17)
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(d0f94c87-cdd0-4eac-bcee-6526cd07f1ae)
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-07-30T15:47:58.0530413+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:47:58.0530413+00:00 }, CommitPosition { Position: 0, Sequence: 4, Timestamp: 2026-07-30T15:47:58.0530413+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-07-30T15:47:58.0530413+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:47:58.0530413+00:00 })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-07-30T15:47:58.0530413+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:47:58.0530413+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-07-30T15:47:58.0530413+00:00 }, CommitPosition { Position: 0, Sequence: 8, Timestamp: 2026-07-30T15:47:58.0530413+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:47:58.0530413+00:00 })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-07-30T15:48:03.8031870+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:48:03.8031870+00:00 }, CommitPosition { Position: 0, Sequence: 4, Timestamp: 2026-07-30T15:48:03.8031870+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-07-30T15:48:03.8031870+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:48:03.8031870+00:00 })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-07-30T15:48:03.8031870+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:48:03.8031870+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-07-30T15:48:03.8031870+00:00 }, CommitPosition { Position: 0, Sequence: 8, Timestamp: 2026-07-30T15:48:03.8031870+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:48:03.8031870+00:00 })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-07-30T15:48:05.1188883+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:48:05.1188883+00:00 }, CommitPosition { Position: 0, Sequence: 4, Timestamp: 2026-07-30T15:48:05.1188883+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-07-30T15:48:05.1188883+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:48:05.1188883+00:00 })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-07-30T15:48:05.1188883+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:48:05.1188883+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-07-30T15:48:05.1188883+00:00 }, CommitPosition { Position: 0, Sequence: 8, Timestamp: 2026-07-30T15:48:05.1188883+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-07-30T15:48:05.1188883+00:00 })

♻️ This comment has been updated with latest results.

alexeyzimarevand others added 6 commits March 13, 2026 18:30
- Add TrySetDefault to EventSerializer using Interlocked.CompareExchange
so DefaultEventSerializer constructor doesn't overwrite an existing
serializer configuration
- Add module initializer to Eventuous.Tests.Subscriptions.Base so
integration tests (Azure, Kafka, KurrentDB) have EventSerializer.Default
configured before use
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These test projects use EventSerializer.Default without configuring it.
Add module initializers with DefaultEventSerializer to fix CI failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use EventSerializer.SetDefault() explicitly instead of relying on
the constructor's hidden TrySetDefault side effect.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Dynamic
The old name was ambiguous since DefaultStaticEventSerializer also uses
System.Text.Json. The new name clarifies this package contains the
reflection-based (non-AOT) dynamic serializer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite the default serializer section to document both
DefaultStaticEventSerializer (AOT, recommended) and
DefaultEventSerializer (reflection-based, separate package).
Update API references from the old DefaultEventSerializer.SetDefaultSerializer
to the new EventSerializer.SetDefault.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Mar 13, 2026

Copy link
Copy Markdown

Deploying eventuous-main with Cloudflare Pages Cloudflare Pages

Latest commit:f1dae72
Status: ✅ Deploy successful!
Preview URL:https://058533bf.eventuous-main.pages.dev
Branch Preview URL:https://feature-aot-serializer-clean.eventuous-main.pages.dev

View logs

…ing reflection unannotated
Follows the System.Text.Json DefaultJsonTypeInfoResolver pattern: the
IEventSerializer interface and method implementations carry no attributes,
while the reflection-based serializer declares RequiresUnreferencedCode and
RequiresDynamicCode on its constructor. Apps opting into the reflection
serializer get exactly one IL2026/IL3050 warning at the construction site;
apps using DefaultStaticEventSerializer get none. IsAotCompatible is enabled
on the Dynamic package so the trim/AOT analyzers verify the annotations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings July 30, 2026 14:07

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors Eventuous’ event serialization API to be trim/native-AOT friendly by removing trimming/AOT warning attributes from IEventSerializer and its call sites, and moving the reflection-based JSON serializer into a separate “dynamic” package with warnings emitted only at construction.

Changes:

  • Introduces EventSerializer as the new static default serializer holder (DefaultEventSerializer.Instance/SetDefaultSerializer()EventSerializer.Default/SetDefault()).
  • Moves the reflection-based DefaultEventSerializer into a new Eventuous.Serialization.Json.Dynamic project, annotating its constructor for trim/AOT warnings while keeping the IEventSerializer surface warning-free.
  • Updates core + integrations + tests/samples/docs to reference the new default holder and (where needed) the new dynamic serialization project.

Reviewed changes

Copilot reviewed 82 out of 82 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/Sqlite/test/Eventuous.Tests.Sqlite/Eventuous.Tests.Sqlite.csprojAdds reference to the new dynamic JSON serializer project for tests.
src/Sqlite/src/Eventuous.Sqlite/SqliteStore.csRemoves trimming/AOT attributes from methods; relies on serializer choice instead.
src/Relational/src/Eventuous.Sql.Base/Subscriptions/SqlSubscriptionBase.csRemoves trimming/AOT attributes from polling/subscribe path.
src/Relational/src/Eventuous.Sql.Base/SqlEventStoreBase.csSwitches fallback serializer from DefaultEventSerializer.Instance to EventSerializer.Default and removes trim/AOT attributes.
src/Relational/src/Eventuous.Sql.Base/Producers/UniversalProducer.csRemoves trim/AOT attributes from producer API.
src/Relational/src/Eventuous.Sql.Base/Constants.csRemoves now-unused dynamic-serialization warning message constant.
src/Redis/test/Eventuous.Tests.Redis/Fixtures/IntegrationFixture.csUpdates test default serializer setup to EventSerializer.SetDefault.
src/Redis/test/Eventuous.Tests.Redis/Eventuous.Tests.Redis.csprojAdds reference to the dynamic JSON serializer project for tests.
src/Redis/src/Eventuous.Redis/RedisStore.csSwitches fallback serializer to EventSerializer.Default.
src/RabbitMq/src/Eventuous.RabbitMq/Subscriptions/RabbitMqSubscription.csRemoves trim/AOT attributes from subscription methods.
src/RabbitMq/src/Eventuous.RabbitMq/Producers/RabbitMqProducer.csSwitches fallback serializer to EventSerializer.Default and removes trim/AOT attributes.
src/RabbitMq/src/Eventuous.RabbitMq/Constants.csRemoves now-unused dynamic-serialization warning message constant.
src/Mongo/test/Eventuous.Tests.Projections.MongoDB/Fixtures/IntegrationFixture.csUpdates test default serializer setup to EventSerializer.SetDefault.
src/Mongo/test/Eventuous.Tests.Projections.MongoDB/Eventuous.Tests.Projections.MongoDB.csprojAdds reference to the dynamic JSON serializer project for tests.
src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/CustomDependenciesTests.csUpdates test serializer delegation to use EventSerializer.Default.
src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/StreamSubscription.csRemoves trim/AOT attributes from subscription methods.
src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/PersistentSubscriptionBase.csRemoves trim/AOT attributes from subscription methods.
src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/AllStreamSubscription.csRemoves trim/AOT attributes from subscription methods.
src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDBProducer.csSwitches fallback serializer to EventSerializer.Default and removes trim/AOT attributes.
src/KurrentDB/src/Eventuous.KurrentDB/KurrentDBEventStore.csSwitches fallback serializer to EventSerializer.Default and removes trim/AOT attributes.
src/KurrentDB/src/Eventuous.KurrentDB/Constants.csRemoves now-unused dynamic-serialization warning message constant.
src/Kafka/test/Eventuous.Tests.Kafka/BasicProducerTests.csUpdates test deserialization to use EventSerializer.Default.
src/Kafka/src/Eventuous.Kafka/Producers/KafkaBasicProducer.csSwitches fallback serializer to EventSerializer.Default.
src/GooglePubSub/src/Eventuous.GooglePubSub/Producers/GooglePubSubProducer.csSwitches fallback serializer to EventSerializer.Default.
src/Gateway/test/Eventuous.Tests.Gateway/TestSetup.csAdds module initializer to set a default serializer for tests.
src/Gateway/test/Eventuous.Tests.Gateway/Eventuous.Tests.Gateway.csprojAdds reference to the dynamic JSON serializer project for tests.
src/Extensions/test/Eventuous.Sut.AspNetCore/Program.csUpdates SUT default serializer setup to EventSerializer.SetDefault.
src/Extensions/test/Eventuous.Sut.AspNetCore/Eventuous.Sut.AspNetCore.csprojAdds reference to the dynamic JSON serializer project for the SUT.
src/Experimental/src/ElasticPlayground/Program.csUpdates sample/playground default serializer setup to EventSerializer.SetDefault.
src/Experimental/src/ElasticPlayground/ElasticPlayground.csprojAdds reference to the dynamic JSON serializer project for the playground.
src/Core/test/Eventuous.Tests.Subscriptions/TestSetup.csAdds module initializer to set a default serializer for tests.
src/Core/test/Eventuous.Tests.Subscriptions/Eventuous.Tests.Subscriptions.csprojAdds reference to the dynamic JSON serializer project for tests.
src/Core/test/Eventuous.Tests.Subscriptions.Base/TestSetup.csAdds module initializer to set a default serializer for tests.
src/Core/test/Eventuous.Tests.Subscriptions.Base/Eventuous.Tests.Subscriptions.Base.csprojAdds reference to the dynamic JSON serializer project for tests.
src/Core/test/Eventuous.Tests.Persistence.Base/Eventuous.Tests.Persistence.Base.csprojAdds reference to the dynamic JSON serializer project for tests.
src/Core/src/Eventuous.Subscriptions/IMessageSubscription.csRemoves trim/AOT attributes from subscription interface.
src/Core/src/Eventuous.Subscriptions/EventSubscriptionWithCheckpoint.csRemoves trim/AOT attributes (and now-unused CodeAnalysis using).
src/Core/src/Eventuous.Subscriptions/EventSubscription.csSwitches fallback serializer to EventSerializer.Default and removes trim/AOT attributes (incl. avoiding name collision).
src/Core/src/Eventuous.Subscriptions/Constants.csRemoves now-unused dynamic-serialization warning message constant.
src/Core/src/Eventuous.Serialization/IEventSerializer.csRemoves Requires* attributes and embedded warning-message constants from the interface.
src/Core/src/Eventuous.Serialization/EventSerializer.csAdds new static holder for configuring/obtaining the default serializer.
src/Core/src/Eventuous.Serialization/DefaultStaticEventSerializer.csRemoves unconditional suppression attributes from the AOT/static serializer methods.
src/Core/src/Eventuous.Serialization/DefaultEventSerializer.csRemoves old reflection-based serializer implementation from the core package.
src/Core/src/Eventuous.Serialization.Json.Dynamic/Eventuous.Serialization.Json.Dynamic.csprojAdds new dynamic serializer project (AOT analyzer enabled).
src/Core/src/Eventuous.Serialization.Json.Dynamic/DefaultEventSerializer.csAdds the reflection-based JSON serializer with constructor-level Requires* annotations.
src/Core/src/Eventuous.Producers/ProducerExtensions.csRemoves trim/AOT attributes from producer extension APIs.
src/Core/src/Eventuous.Producers/IProducer.csRemoves trim/AOT attributes from producer interfaces/default implementation.
src/Core/src/Eventuous.Producers/Constants.csRemoves now-unused dynamic-serialization warning message constant.
src/Core/src/Eventuous.Producers/BaseProducer.csRemoves trim/AOT attributes from base producer APIs.
src/Core/src/Eventuous.Persistence/StateStore/StateStoreFunctions.csRemoves trim/AOT attributes from state store extension functions.
src/Core/src/Eventuous.Persistence/StateStore/StateStore.csSwitches fallback serializer to EventSerializer.Default and removes trim/AOT attributes.
src/Core/src/Eventuous.Persistence/StateStore/IStateStore.csRemoves trim/AOT attributes from obsolete interface method.
src/Core/src/Eventuous.Persistence/EventStore/TieredEventStore.csRemoves trim/AOT attributes from tiered store APIs.
src/Core/src/Eventuous.Persistence/EventStore/TieredEventReader.csRemoves trim/AOT attributes from tiered reader APIs.
src/Core/src/Eventuous.Persistence/EventStore/StoreFunctions.csRemoves trim/AOT attributes from event store extension methods.
src/Core/src/Eventuous.Persistence/EventStore/IEventWriter.csRemoves trim/AOT attributes from writer interface methods/default impl.
src/Core/src/Eventuous.Persistence/EventStore/IEventReader.csRemoves trim/AOT attributes from reader interface methods.
src/Core/src/Eventuous.Persistence/Diagnostics/Tracing/TracedEventWriter.csRemoves trim/AOT attributes from traced writer methods.
src/Core/src/Eventuous.Persistence/Diagnostics/Tracing/TracedEventStore.csRemoves trim/AOT attributes from traced store methods.
src/Core/src/Eventuous.Persistence/Diagnostics/Tracing/TracedEventReader.csRemoves trim/AOT attributes from traced reader methods.
src/Core/src/Eventuous.Persistence/Constants.csRemoves now-unused dynamic-serialization warning message constant.
src/Core/src/Eventuous.Persistence/AggregateStore/IAggregateStore.csRemoves trim/AOT attributes from obsolete aggregate store APIs.
src/Core/src/Eventuous.Persistence/AggregateStore/AggregateStoreWithArchive.csRemoves trim/AOT attributes from archive aggregate store methods.
src/Core/src/Eventuous.Persistence/AggregateStore/AggregateStoreExtensions.csRemoves trim/AOT attributes from obsolete aggregate store extension methods.
src/Core/src/Eventuous.Persistence/AggregateStore/AggregateStore.csRemoves trim/AOT attributes from obsolete aggregate store implementation methods.
src/Core/src/Eventuous.Persistence/AggregateStore/AggregatePersistenceExtensions.csRemoves trim/AOT attributes from aggregate persistence extension methods.
src/Core/src/Eventuous.Application/ThrowingCommandService.csRemoves trim/AOT attributes from command handling wrapper.
src/Core/src/Eventuous.Application/Persistence/WriterExtensions.csRemoves trim/AOT attributes from application writer extensions.
src/Core/src/Eventuous.Application/ICommandService.csRemoves trim/AOT attributes from command service interface.
src/Core/src/Eventuous.Application/FunctionalService/CommandService.csRemoves trim/AOT attributes from functional command service handler entrypoint.
src/Core/src/Eventuous.Application/Diagnostics/TracedCommandService.csRemoves trim/AOT attributes from traced command service handler entrypoint.
src/Core/src/Eventuous.Application/AggregateService/CommandService.csRemoves trim/AOT attributes from aggregate command service handler entrypoint.
src/Azure/test/Eventuous.Tests.Azure.ServiceBus/TestSetup.csAdds module initializer to set a default serializer for tests.
src/Azure/test/Eventuous.Tests.Azure.ServiceBus/ConvertEventToMessage.csUpdates tests to use EventSerializer.Default.
src/Azure/src/Eventuous.Azure.ServiceBus/Producers/ServiceBusProducer.csSwitches fallback serializer to EventSerializer.Default.
samples/postgres/Bookings/Registrations.csUpdates sample configuration to EventSerializer.SetDefault.
samples/postgres/Bookings/Bookings.csprojAdds reference to the dynamic JSON serializer project for the sample.
samples/kurrentdb/Bookings/Registrations.csUpdates sample configuration to EventSerializer.SetDefault.
samples/kurrentdb/Bookings/Program.csUpdates sample configuration to EventSerializer.SetDefault.
samples/kurrentdb/Bookings/Bookings.csprojAdds reference to the dynamic JSON serializer project for the sample.
Eventuous.slnxAdds the new dynamic serializer project to the solution.
docs/src/content/docs/next/persistence/serialisation.mdUpdates docs to describe AOT/static vs dynamic serializers and new configuration mechanism.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4 to +5
using System.Text.Json;
using static Eventuous.DeserializationResult;
Comment on lines +27 to +28
public static void SetDefault(IEventSerializer serializer)
=> _default = serializer ?? throw new ArgumentNullException(nameof(serializer));
Resolutions:
- docs/: deleted; the docs site moved to the eventuous-docs repo, the
serialisation page update is ported there separately
- BaseProducer, SqlEventStoreBase: kept dev's new XML docs, dropped the
removed RequiresDynamicCode/RequiresUnreferencedCode attributes and the
DefaultEventSerializer.Instance fallback
- AllStreamSubscription.PumpMessages (new on dev): dropped the removed
attributes
- SignalR module (new on dev): DefaultEventSerializer.Instance replaced
with EventSerializer.Default, producer attributes dropped, tests set up
the serializer via module initializer like other test projects
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 88 out of 88 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/Core/src/Eventuous.Serialization/EventSerializer.cs:18

  • EventSerializer.Default reads the backing field without a memory barrier, while SetDefault uses a plain assignment. In multi-threaded startup code, another thread can still observe _default as null after SetDefault and throw unexpectedly. Use Volatile.Read for the getter and Interlocked/Volatile for writes to guarantee visibility.
    src/Core/src/Eventuous.Serialization.Json.Dynamic/DefaultEventSerializer.cs:25
  • DefaultEventSerializer's constructor assigns the provided JsonSerializerOptions to a field without validating it. Passing null will register a broken serializer instance and lead to later NullReferenceExceptions during (de)serialization.

The tests relied on the implicit reflection-based serializer default that
this PR removes; EventSerializer.Default now throws when unconfigured.
Register DefaultEventSerializer via a module initializer, same as the
other integration test projects.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings July 30, 2026 15:47

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 90 out of 90 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/Core/src/Eventuous.Serialization/EventSerializer.cs:22

  • EventSerializer.Default reads _default without a memory barrier, while TrySetDefault writes via Interlocked.CompareExchange. To make the default serializer publication reliably visible across threads, use Volatile.Read in the getter and Volatile.Write (or Interlocked.Exchange) in SetDefault.

@alexeyzimarev
alexeyzimarev merged commit 37402bc into devJul 30, 2026
17 checks passed
@alexeyzimarev
alexeyzimarev deleted the feature/aot-serializer-cleanup branch July 30, 2026 16:00
alexeyzimarev added a commit that referenced this pull request Aug 20, 2026
…tions (#574)
* feat(samples): host the KurrentDB Bookings sample in Aspire with blob projections
Supersedes #556: instead of adding a third Bookings clone under
samples/azure, the existing KurrentDB sample gains an Aspire AppHost and
the Azure pieces worth keeping.
- Add Bookings.AppHost orchestrating KurrentDB, MongoDB 7.0, the Azurite
blob emulator, both services, and a Scalar API reference; service
telemetry flows to the Aspire dashboard via OTLP
- Add BookingStateBlobProjection: booking state projected to Azure Blob
Storage from the same all-stream subscription as the Mongo projections,
with ByGlobalPosition idempotency and race retries, exposed via
GET /bookings/{id}/view and readable next to the event-store fold
- Fix latent Payments sample breakage: set the default event serializer
(required since #524), reference the AspNetCore command mapping
generator, and bind RecordPayment with HttpCommand<PaymentState> so
MapDiscoveredCommands actually maps the route
- Add health endpoints and Scalar-compatible OpenAPI document routes to
both services; keep fixed ports for standalone runs while letting
Aspire assign URLs
- Add kurrentdb and azurite services to docker-compose for standalone
runs; add a sample README covering both run modes
- Pin the sample (and the Spyglass tests that use its apps as fixtures)
to net10.0, since Aspire cannot run multi-targeted projects; Spyglass
tests move to a net10-only CI step
- Verified end to end under Aspire: book, pay via the Payments service,
integration events through the gateway, and all three read paths agree
Co-authored-by: Quezlatch <quezlatch@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(samples): review feedback on the blob projection wiring
- Run BookingStateBlobProjection on its own subscription with its own
checkpoint, so adding it to a system with existing data replays the
stream from the beginning and backfills the blobs instead of resuming
from the Mongo projections' position (which would also let a later
payment event build an incomplete view from a fresh instance)
- Create the blob container with the awaited async call at startup
Verified live under Aspire: book + pay end to end, blob view and Mongo
view agree.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Quezlatch <quezlatch@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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

@alexeyzimarev