Skip to content

Process Manager via Handlers sample#2579

Merged
jeremydmiller merged 8 commits intoJasperFx:mainfrom
erikshafer:sample/process-manager-via-handlers
Apr 26, 2026
Merged

Process Manager via Handlers sample#2579
jeremydmiller merged 8 commits intoJasperFx:mainfrom
erikshafer:sample/process-manager-via-handlers

Conversation

@erikshafer
Copy link
Copy Markdown
Contributor

Summary

Adds a worked sample and documentation for building an event-sourced Process Manager using existing Wolverine + Marten features. Complements Sagas for teams that want event-sourced process state, a replayable audit log, and pure-function handler tests. No new framework types, no new packages.

Changes

  • Sample project (src/Samples/ProcessManagerSample/): state type, events, commands, and six handlers (start + three continue + cancel + payment timeout). Registered in wolverine.slnx under /Samples/ProcessManagerSample/.
  • Tests: 20 tests covering happy path, out-of-order delivery, idempotency, cancellation, scheduled timeout firing, and duplicate-start collision. Mix of pure-function unit tests and xUnit + Alba integration tests.
  • Documentation: new docs/guide/durability/marten/process-manager-via-handlers.md with seven sections: Introduction, Building Blocks, Recipe, Worked Example, Friction Points, When to Use Saga Instead, and DCB Enhancement. Added to the Marten Integration nav group in docs/.vitepress/config.mts, immediately after Sagas.
  • DISCOVERIES.md: internal findings log in the sample directory, scoped for the future ProcessManager<TState> framework proposal.

Validation

  • dotnet build on the sample: clean
  • dotnet test on the sample: 20 / 20 green
  • vitepress build docs: clean render, no broken nav links
  • End-to-end live run: dotnet run → HTTP POST → event persisted → scheduler fires timeout → cancellation event appended → inline snapshot projection correct

Notes

  • This is the "you can already build it today" companion effort. It is not the future ProcessManager<TState> framework-type proposal; DISCOVERIES.md is the bridge between the two.
  • Section 5 (The Friction Points) is honest about the tradeoffs against Sagas: no single home for the process, distributed completion logic, two guard lines per continue handler, asymmetric start handler, and no first-class wait-for-scheduled-message test helper.
  • Stretch goals deferred: strong-typed process IDs, a deterministic concurrency-race test (decided docs-only; the exception-hierarchy trap is documented in Section 5 instead), and snippet extraction between doc and sample.

…tion via existing Wolverine + Marten features

Implement worked sample showing process manager pattern using `FetchForWriting`, `[AggregateHandler]`, `MartenOps.StartStream`, and inline snapshots—no new base class required. Includes order fulfillment flow with state type, events, commands, start handler, integration test, and DISCOVERIES.md capturing corrected expectations (start handler must return `IStartStream`, not use `[AggregateHandler]` due to OnMissing defaults). Adds process-manager-via-handlers.md guide skeleton pending implementation phases.

- Add ProcessManagerSample project with OrderFulfillmentState, events, commands, handlers
- Add StartOrderFulfillmentHandler returning IStartStream via MartenOps.StartStream
- Add ProcessManagerSample.Tests with IntegrationContext, AppFixture, when_starting_a_fulfillment
- Add DISCOVERIES.md documenting confirmed/corrected expectations and gotchas
- Add process-manager-via-handlers.md guide structure with building-blocks section
- Add OrderFulfillment/README.md outlining sample architecture
- Wire inline snapshot projection and Wolverine integration in Program.cs
- Add appsettings.json with Marten connection string
…tests for ProcessManagerSample

Implement PaymentConfirmedHandler, ItemsReservedHandler, ShipmentConfirmedHandler, and CancelOrderFulfillmentHandler as aggregate handlers returning `Events` with terminal-state and idempotency guards. Add OrderFulfillmentState `Apply` methods for all events. Expand events with integration types (PaymentConfirmed, ItemsReserved, ShipmentConfirmed) and terminal events (OrderFulfillmentCompleted, OrderFulfillmentCancelled). Add CancelOrderFulfillment compensating command. Include HandlerUnitTests covering pure-function handler logic and integration tests verifying happy-path completion, out-of-order arrival, duplicate handling, and cancellation behavior.

- Add PaymentConfirmedHandler/ItemsReservedHandler/ShipmentConfirmedHandler with completion guards
- Add CancelOrderFulfillmentHandler appending OrderFulfillmentCancelled
- Add OrderFulfillmentState.Apply methods for all event types
- Add PaymentConfirmed, ItemsReserved, ShipmentConfirmed, OrderFulfillmentCompleted, OrderFulfillmentCancelled events
- Add CancelOrderFulfillment command with Reason property
- Add HandlerUnitTests verifying guard logic, completion detection, and idempotency
- Add when_completing_a_fulfillment tests for happy path, out-of-order, duplicates
- Add when_cancelling_a_fulfillment tests for mid-process cancel and post-cancel no-ops
- Expand process-manager-via-handlers.md with complete Step 3–8 recipe and testing guidance
…ncing via state guards

Implement `PaymentTimeoutHandler` as pure aggregate handler using terminal-state and payment-confirmed guards to make timeout a silent no-op when process completes or payment arrives before scheduler fires. Extend `StartOrderFulfillmentHandler` to schedule `PaymentTimeout` via `(IStartStream, OutgoingMessages)` tuple return with configurable delay. Add comprehensive timeout tests covering unit-level guard behavior, scheduler-fired cancellation, and early-payment silencing with polling helper for scheduler observation.

- Add PaymentTimeoutHandler returning Events with IsTerminal/PaymentConfirmed guards
- Add PaymentTimeout command with OrderFulfillmentStateId correlation
- Extend StartOrderFulfillmentHandler tuple return scheduling PaymentTimeout via OutgoingMessages.Delay
- Add PaymentTimeoutWindow optional parameter to StartOrderFulfillment for test override
- Add DefaultPaymentTimeoutWindow constant (15 minutes) to StartOrderFulfillmentHandler
- Add when_payment_times_out tests: unit guards, scheduler cancellation, payment-before-timeout silencing
- Add WaitForCondition polling helper for scheduler integration test observation
- Update DISCOVERIES.md documenting tuple-return composition, scheduler timing, and "let state decide" idiom
- Update recipe adjustments removing "to be validated" banner and recommending state-guard pattern
…on, testing patterns, friction points, and Saga comparison

Finalize Phase 5–6 sections removing "to be validated" banner and adding timeout handler, scheduler integration tests, comprehensive friction-point analysis, and Saga decision criteria. Clarify tuple-return composition for `(IStartStream, OutgoingMessages)`, document "let state decide" pattern avoiding explicit timeout cancellation, and provide `WaitForCondition` polling helper for scheduler-fired integration tests. Expand friction points with honest accounting of distributed completion logic, guard-line overhead, and silent failure modes. Add when-to-use-Saga guidance covering discoverability, framework-managed lifecycle, and team fluency trade-offs.

- Remove "to be validated" banner from Step 6 section
- Clarify tuple-return unpacker behavior composing IMartenOp with OutgoingMessages
- Add PaymentTimeoutHandler example with terminal-state and payment-confirmed guards
- Document "let state decide" idiom vs explicit-cancel approach for timeouts
- Add WaitForCondition polling helper for scheduler integration test observation
- Expand testing section with scheduler-fired message patterns and timing guidance
- Add complete "The Friction Points
…o process-manager-via-handlers guide

Replace placeholder with comprehensive reference implementation from ProcessManagerSample including OrderFulfillmentState projection, all event/command types, handler implementations (start, continue steps, cancel, timeout), Marten+Wolverine wiring, unit tests demonstrating pure-function handler testing, and integration tests covering happy-path end-to-end flow. Add DCB enhancement section documenting `[BoundaryModel]` usage for cross-stream invariants with EventTagQuery patterns and sharp edges. Update navigation to include new guide in Marten durability sidebar.

- Add complete OrderFulfillmentState.cs with Apply methods and terminal-state guards
- Add Events.cs and Commands.cs with all domain types
- Add all handler implementations: Start, PaymentConfirmed, ItemsReserved, ShipmentConfirmed, Cancel, PaymentTimeout
- Add Program.cs showing Marten snapshot projection and Wolverine integration wiring
- Add unit test example for pure-function payment-confirmed completion logic
- Add integration test example verifying happy-path event stream and projected state
- Add DCB enhancement section with BoundaryModel usage, EventTagQuery patterns, and concurrency gotchas
- Add guide to .vitepress/config.mts Marten durability navigation section
…lag ConcurrencyException scoping gap

Expand process-manager-via-handlers guide to document `ExistingStreamIdCollisionException` behavior when duplicate start commands arrive: explain exception type, propagation through `InvokeMessageAndWaitAsync`, transaction rollback, and idempotent handling strategies. Add `starting_the_same_process_twice_throws_and_first_start_wins` test verifying exception properties and first-start-wins semantics. Flag in Friction Points that `ExistingStreamIdCollisionException` inherits from `MartenException` not `ConcurrencyException`, requiring broader retry policy scoping to catch duplicate-start failures.

- Add duplicate-start exception documentation in Step 4a corollary paragraph
- Add starting_the_same_process_twice_throws_and_first_start_wins test with rollback verification
- Add friction point note flagging ExistingStreamIdCollisionException inheritance gotcha
- Close DISCOVERIES.md question 3 documenting exception behavior and test reference
… discoveries

Refine the "Process Manager via Handlers" guide and associated sample project files to reflect the final implementation state. This includes clarifying the asymmetry between start and continue handlers, updating test counts, and documenting final architectural decisions.

- Clarify start vs. continue handler asymmetry in guide and sample README
- Update test counts and implementation notes to match the full sample suite
- Finalize DISCOVERIES.md with a historical record of applied recipe adjustments
- Document decision to keep concurrency race behavior as docs-only to avoid flaky integration tests
- Add focus notes to simplified code snippets in documentation for better clarity
@jeremydmiller jeremydmiller merged commit b67c614 into JasperFx:main Apr 26, 2026
18 of 21 checks passed
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.

2 participants