Skip to content

docs(oes): Egil.Orleans.Messaging API design & project scaffold#33

Draft
egil wants to merge 15 commits into
mainfrom
egil/egil-orleans-cqrs-design
Draft

docs(oes): Egil.Orleans.Messaging API design & project scaffold#33
egil wants to merge 15 commits into
mainfrom
egil/egil-orleans-cqrs-design

Conversation

@egil
Copy link
Copy Markdown
Owner

@egil egil commented May 23, 2026

Egil.Orleans.Messaging — API Design & Project Scaffold

A toolbox of composable building blocks for Orleans grains that need atomic state writes, transactional outbox, receiver-side dedup, and stream subscription management. Pick what you need, leave the rest.

What's in this PR

API design document (api-design.md) — comprehensive design covering all settled decisions:

  • §0 Scope & packaging — single NuGet, flat namespace
  • §1 IStateManager<T> — atomic writes with in-flight recovery, committed-state fence
  • §2 Outbox storage placement — atomic with grain state
  • §3 Outbox<T> — sealed class, O(1) fingerprint equality, epoch semantics
  • §3a VersionedState<TSelf> — version-based equality immune to ImmutableArray traps
  • §4 MessageTracker — receiver-side dedup for streams + outbox
  • §5 StreamManager — fluent subscribe/resume/error facade, EH adapter, OTel trace correlation
  • §6 Opinionated grain pattern — functional commands, interleaved reads (guidance only)
  • §7 OutboxProcessor<T> — timer + reminder driven dispatch with postmen
  • §8 Naming, serialization & telemetry — Orleans aliases, STJ converters, non-serialized fields
  • §9 Test strategy — InProcessTestCluster, coverage targets, serialization round-trips

Project scaffold — solution, csproj files, Directory.Packages.props, version.json, global.json.

Status

Design document for review. Public type stubs (with XML doc comments) coming next as a sanity check before implementation.

egil and others added 11 commits May 23, 2026 18:42
Comprehensive API design document covering all 8 design sections:

- §0: Scope, packaging (one NuGet, split-ready), name settled as
  Egil.Orleans.Messaging
- §1: IStateManager<T> atomic writes with in-flight recovery
- §2: Outbox<T> storage placement (co-located on grain state)
- §3: Outbox<T> sealed class shape, epoch semantics, fingerprint
  equality, configurable max depth
- §3a: VersionedState<TSelf> for ImmutableArray equality trap
- §4: MessageTracker sealed class, dual-dictionary dedup
- §5: StreamManager fluent builder facade
- §6: Opinionated functional-grain pattern (guidance, not enforced)
- §7: OutboxProcessor<T> grain-scoped timer+reminder dispatch with
  callback-based postmen, per-attempt error tracking
- §8: Naming (flat namespace), serialization ([Alias] with fully
  qualified prefix, sequential [Id]), telemetry conventions

All decisions settled via structured grilling sessions. Directory
still named Egil.Orleans.CQRS — rename deferred to project scaffold.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ationale

IStateManager<T> keeps its wrapper design — the committed-state fence
is the real value, not just recovery logic. Extension methods cannot
hide IPersistentState<T>.State from [AlwaysInterleave] read methods.

Settled decisions:
- AsStateManager() extension method factory on IPersistentState<T>
- No DI registration needed for v1
- Future: IStateManagerFactory in DI for provider-specific overrides
- Interface + monolithic default StateManager<T>, provider-specific
  implementations as opt-in separate packages later
- Updated §6 to reference committed-state fence for safe interleaved
  reads

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
InProcessTestCluster for all tests (no mocks). Coverage targets:
100% branch on core types (Outbox, MessageTracker, StateManager,
VersionedState, OutboxProcessor), 95% on supporting types.

Test grains per behavior: write recovery, outbox drain, dedup,
interleaved reads, stuck postman, multi-reminder.

Serialization round-trip tests for all [GenerateSerializer] types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All serializable types get [JsonConverter] attributes — STJ discovers
converters automatically, zero user registration needed.

Custom JsonConverter<T> for types with private fields (Outbox<T>,
MessageTracker, envelopes, tokens). JsonConverterFactory for open
generic types. [JsonInclude] for VersionedState.Version (internal set).

Newtonsoft.Json not supported — documented as known limitation.

Updated §9 test strategy to include STJ round-trip tests alongside
Orleans serialization tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Belt-and-suspenders: service-reference fields like TimeProvider _time
get [NonSerialized] + [JsonIgnore] in addition to having no [Id].
Prevents accidental serialization across Orleans, STJ, and reflection-
based serializers. Custom converters also skip them explicitly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ry detail

Restored full content from original design doc:
- Custom EH adapter pattern for EnrichedEventHubSequenceToken with
  code sample showing UseDataAdapter wiring
- OpenTelemetry trace correlation: ActivityLinks (not parent chaining),
  producer stashes traceparent, consumer parses + links
- Expanded telemetry section with full metric descriptions including
  end-to-end lag via EnrichedEventHubSequenceToken

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename directory from Egil.Orleans.CQRS to Egil.Orleans.Messaging.
Add solution file, Directory.Packages.props, version.json, global.json,
src csproj (library), and test csproj.

Package description: toolbox of composable building blocks — not a
framework. Pick what you need, leave the rest.

[skip notes]

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Stub IStateManager<T>, VersionedState, WritePolicy, OutboxSequenceToken,
OutboxMessageEnvelope<T>, and StateManagerExtensions with thorough XML
documentation covering constraints, threading semantics, recovery
behavior, serialization requirements, and usage examples.

VersionedState is non-generic only — recovery path uses pattern matching
and direct Version comparison, not Equals, so the generic CRTP layer
was removed as unnecessary.

IStateManager<T> docs include two-tier deep immutability requirement:
required with [AlwaysInterleave], strongly recommended without.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Stub all public types and interfaces for Egil.Orleans.Messaging with
detailed XML documentation covering behavior, constraints, and usage
patterns. Types include:

- Outbox<T> — immutable outbox collection with O(1) fingerprint equality
- MessageTracker — idempotent stream + outbox dedup with eviction
- StreamManager/StreamManagerBuilder — fluent stream subscription facade
- StreamCursor — (StreamId, token) wrapper with TryGetEnqueuedTime
- OutboxProcessor<T> — timer/reminder-driven outbox dispatch
- OutboxProcessorOptions<T> — callback-based postman registration
- IOutboxGrain — marker interface with IRemindable DIM
- EnrichedEventHubSequenceToken — EventHubSequenceTokenV2 + EnqueuedTime
- NoPostmanRegisteredException — diagnostic exception for unmatched items
- OutboxProcessorExtensions — InitializeOutboxProcessor DI method

Also updates api-design.md §3a to reflect removal of the generic
VersionedState<TSelf> layer (broken compiler-generated Equals).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add EnrichedEventHubAdapter (public, not sealed) that subclasses
EventHubDataAdapter to produce EnrichedEventHubSequenceToken instances
carrying broker-side EnqueuedTime and StreamProviderName.

- EnrichedEventHubSequenceToken gains StreamProviderName property for
  provider-aware dedup and edge case handling
- UseEnrichedDataAdapter extension on IEventHubStreamConfigurator for
  one-line registration of the library adapter
- StreamCursor gains TryGetStreamProviderName() alongside existing
  TryGetEnqueuedTime()
- Adapter is public/unsealed for users needing custom stream adapters;
  they register via UseDataAdapter directly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add internal implementation stubs:

- StateManager<T>: core IStateManager<T> impl with committed-state
  fence, version stamping, and write recovery. Thorough XML docs
  covering recovery matrix and concurrency model.
- OutboxJsonConverterFactory: STJ factory for Outbox<T>
- OutboxMessageEnvelopeJsonConverterFactory: STJ factory for envelope
- MessageTrackerJsonConverter: STJ converter for MessageTracker
- OutboxSequenceTokenJsonConverter: STJ converter for OutboxSequenceToken
- StreamCursorJsonConverter: STJ converter for StreamCursor with
  polymorphic token support

All converters registered via [JsonConverter] attribute on their
respective public types (attributes still commented out — converters
not yet implemented).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@egil egil force-pushed the egil/egil-orleans-cqrs-design branch from aacdc53 to 2e10b28 Compare May 23, 2026 21:09
egil and others added 4 commits May 23, 2026 22:03
Implement Outbox<T> mutation and O(1) equality behavior according to the design contract. Add behavior tests covering sequence assignment, FIFO removal, metadata preservation, and sequence-window equality.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.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