Explore optional typed command and event unions for exhaustive handling
Maintainer thường phản hồi trong vòng 7 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 35/100
- Loại issue
- Tính năng
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- csharp
- Lĩnh vực
- backend-api-design, devtools
Hướng nghiên cứu
Start by reading src/Core/src/Eventuous.Domain/State.cs, then compare the command and aggregate event APIs, target frameworks, and serialization paths named in the issue. Build a small end-to-end prototype with explicit command and event sets, and verify omitted and out-of-set cases produce errors while existing payloads replay unchanged. Done means the prototype, compatibility and runtime notes, and an API recommendation address the acceptance criteria.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Is your feature request related to a problem? Please describe.
Adding a command or event can leave handling incomplete without producing a compiler diagnostic. Eventuous provides typed handler registration, but it does not declare a closed set of commands and events for an aggregate against which handling can be checked.
For example, an application adds BookingCancelled and starts emitting it, but forgets to update the booking state. The default State<T>.When(object) returns the existing state when no handler is registered. Ignoring an event can be intentional, but an accidental omission can produce incorrect state during execution and replay. Similarly, a missing command registration is detected when the command is handled rather than when the application is compiled.
A community suggestion on Discord describes using F# discriminated unions and generics in Rosetta to make these omissions visible during compilation.
Describe the solution you'd like
Explore an optional API that lets an application declare the complete command and event sets for an aggregate and use exhaustive handling at its command and state-transition boundaries.
The initial deliverable should be a small prototype and design recommendation. Keep the existing APIs available and preserve their behavior for applications that do not opt in.
Example: forgetting to apply a cancellation event
An application initially supports creating a booking. Its declared event set, BookingEvent, contains only RoomBooked, and its state-update function handles that event.
A developer then adds cancellation:
- Add
BookingCancelledto theBookingEventset. - Add a command handler that emits
BookingCancelled. - Forget to add cancellation handling to the state-update function.
With the current default state handling, the application can compile and store the cancellation event while leaving the booking state unchanged.
With the proposed typed API, the compiler knows that BookingEvent has two possible cases, but the state-update function handles only one. It reports the missing BookingCancelled case. Adding that case to update the booking's status resolves the diagnostic.
The same principle applies to commands: adding a command to the declared command set should produce a diagnostic wherever exhaustive command handling omits it.
The prototype should demonstrate build failures with the relevant diagnostics treated as errors. A general catch-all can hide newly added cases, so the example should use explicit cases, including explicit no-op handling where appropriate.
Exhaustiveness comes from the closed set and matching semantics. A marker interface alone does not provide this guarantee. These checks cover known cases in the consuming compilation; they do not prove business correctness, registration completeness elsewhere, or compatibility with unknown persisted events.
Design questions to resolve:
- API placement: Can typed adapters or additional generic abstractions support both aggregate-based and functional command services without replacing existing APIs?
- Language and runtime requirements: Compare F# unions, C# unions, and analyzer/generator approaches. C# union declarations produce structs, while current command handling and aggregate event application have
classconstraints. Account for those constraints and Eventuous's supported target frameworks. - Persistence compatibility: Keep existing event type names and serialized payloads stable. Determine how concrete events are wrapped and unwrapped at typed boundaries, including historical event versions and unknown-event behavior.
- Selective consumption: Keep projection and subscription event sets independent. A projection that consumes a subset of events should not be required to handle every event in the system.
Acceptance criteria for this exploration:
- Provide one end-to-end aggregate or functional service example with explicit command and event sets.
- Demonstrate diagnostics for an omitted command case and an omitted event case after adding them to their respective sets.
- Demonstrate rejection of commands or events outside the declared set at the typed API boundary.
- Demonstrate explicit intentional ignoring without a catch-all that hides future cases.
- Verify that existing serialized event payloads can be replayed through the prototype without changing their storage format.
- Document compatibility, required language/runtime versions, and which guarantees remain outside compile-time checking.
- Recommend an API direction, or explain why the tradeoffs do not justify adopting one.
Describe alternatives you've considered
- Retain the current APIs and add application tests that check expected handler registrations and replay behavior.
- Add opt-in startup validation against explicitly declared command/event sets. This catches omissions earlier than message processing, but still at runtime.
- Extend Roslyn analyzers or source generators to check declared sets against handler registrations. The existing
EventUsageAnalyzerchecks event type annotations; it does not establish exhaustive command or event handling. - Keep unions in application domain code and adapt to Eventuous at the boundary, minimizing changes to the library.
Additional context
This proposal originates from the Discord discussion, which shared Rosetta's approach as inspiration for Eventuous.
Rosetta's aggregate example demonstrates typed command and event unions with exhaustive matching. Microsoft's C# union tutorial describes the preview feature, and the language reference explains exhaustiveness diagnostics and the generated struct representation. Evaluate the supported feature and runtime requirements during the prototype rather than assuming a particular release commitment.
- Ngôn ngữ chính
- C#
- Star
- 512
- Fork
- 99
- Merge trung bình
- 5 ngày 6 giờ
- Pull request đã merge (30 ngày)
- 5
Chuẩn bị môi trường
- Có Dockerfile hoặc tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của Eventuous/eventuous
-
MongoDB integration tests fail on newer GitHub Actions runner kernelsCó thể đã có người làm @nmummau đã nhận 2 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
Eventuous/eventuous#626 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 7 ngày
-
`dotnet format style --verify-no-changes` fails with naming violations and unnecessary importsCó thể đã có người làm @nmummau đã nhận 2 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 80/100
Eventuous/eventuous#624 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 7 ngày
-
SQL Server: `append_events` does not need the `@inserted` table variableCó thể đã có người làm @nmummau đã nhận 8 ngày trước. Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 20/100
Eventuous/eventuous#619 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 7 ngày
-
SQL Server: stream reads do one key lookup per event, and `IDX_EventsStream` duplicates `UQ_StreamIdAndStreamPosition`Có thể đã có người làm @nmummau đã nhận 8 ngày trước. Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 18/100
Eventuous/eventuous#618 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 7 ngày
-
SQL Server: every stream lookup by name does a key lookup to get `Version`Có thể đã có người làm @nmummau đã nhận 8 ngày trước. Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 25/100
Eventuous/eventuous#617 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 7 ngày
Tất cả issue của Eventuous/eventuous
Issue tương tự
-
area: client bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug mcp misleading small
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
-
enhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
hazeliscoding/gil-sweep#25 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Bug Platform: Android
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
getsentry/sentry-dotnet#5764 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày