Emit compile-time IEventConvertible implementations from [Event]-annotated classes, eliminating the runtime reflection path in EventFactory and shifting annotation errors left to the build.
The problem today: EventFactory.CreateEventFromData() resolves [Event] metadata via Type.GetCustomAttribute<>() on every publish call, allocating attribute objects and walking type metadata at runtime even in high-throughput paths. The IEventConvertible escape hatch already exists — the benchmarks show it is measurably faster — but it requires repetitive, hand-written ToCloudEvent() boilerplate on every event class. Annotation mistakes (missing DataVersion, non-public class) surface as ArgumentException at runtime rather than at build time.
What we will build: A Deveel.Events.Generators package — a Roslyn incremental source generator (targeting netstandard2.0 as required by the analyzer SDK) that:
- Detects every
partial class decorated with [Event] in the current compilation.
- Emits a generated partial class body that implements
IEventConvertible.ToCloudEvent(): all CloudEvents envelope values (Type, DataSchema, DataContentType) are sourced from annotation values captured at compile time; the Data field is populated via System.Text.Json serialisation — zero reflection at call time.
- Emits compile-time diagnostics:
DLEVT001 — [Event] is applied to a non-partial class (generator cannot act; reflection path is used as a silent fallback).
DLEVT002 — [Event] specifies neither a DataVersion nor an absolute DataSchema URI.
DLEVT003 — [Event]-annotated class is not public.
Benefits:
- Zero reflection on the publish hot-path — all attribute lookup is resolved at build time, matching the performance of hand-coded
IEventConvertible implementations as measured in the existing benchmarks.
DLEVT001 / DLEVT002 / DLEVT003 shift errors that today manifest as ArgumentException at runtime into compile-time build failures, giving developers immediate feedback.
- No breaking change — classes not declared
partial continue to work via the existing reflection path; adopting the generator is a purely opt-in, incremental migration.
- Works correctly in trimmed and ahead-of-time (AOT) compiled deployments where
GetCustomAttribute calls on user types may be stripped.
The problem today:
EventFactory.CreateEventFromData()resolves[Event]metadata viaType.GetCustomAttribute<>()on every publish call, allocating attribute objects and walking type metadata at runtime even in high-throughput paths. TheIEventConvertibleescape hatch already exists — the benchmarks show it is measurably faster — but it requires repetitive, hand-writtenToCloudEvent()boilerplate on every event class. Annotation mistakes (missingDataVersion, non-public class) surface asArgumentExceptionat runtime rather than at build time.What we will build: A
Deveel.Events.Generatorspackage — a Roslyn incremental source generator (targetingnetstandard2.0as required by the analyzer SDK) that:partialclass decorated with[Event]in the current compilation.IEventConvertible.ToCloudEvent(): all CloudEvents envelope values (Type,DataSchema,DataContentType) are sourced from annotation values captured at compile time; theDatafield is populated viaSystem.Text.Jsonserialisation — zero reflection at call time.DLEVT001—[Event]is applied to a non-partialclass (generator cannot act; reflection path is used as a silent fallback).DLEVT002—[Event]specifies neither aDataVersionnor an absoluteDataSchemaURI.DLEVT003—[Event]-annotated class is notpublic.Benefits:
IEventConvertibleimplementations as measured in the existing benchmarks.DLEVT001/DLEVT002/DLEVT003shift errors that today manifest asArgumentExceptionat runtime into compile-time build failures, giving developers immediate feedback.partialcontinue to work via the existing reflection path; adopting the generator is a purely opt-in, incremental migration.GetCustomAttributecalls on user types may be stripped.