Skip to content

Build Bobcat.CritterStack package — combined Wolverine + Marten patterns #10

@jeremydmiller

Description

@jeremydmiller

Goal

Bobcat.CritterStack is the layer above Bobcat.Wolverine and Bobcat.Marten for the most common pattern in the Critter Stack: tracked-session message dispatch and event-store assertion in the same scenario. Item #3 in CONTEXT.md "Next Steps".

This package depends on #9 (Bobcat.Marten) and Bobcat.Wolverine (already in the tree).

Surface to deliver

namespace Bobcat.CritterStack;

public static class CritterStackStepContextExtensions
{
    // Aggregate handler workflow — the canonical Wolverine + Marten pattern.
    // Sends a command, waits for the tracked session to settle, and returns
    // both the resulting events and the rebuilt aggregate so step assertions
    // can compare against expected state.
    Task<AggregateExecution<T>> ExecuteAggregateCommandAsync<T>(
        this IStepContext context,
        object command,
        Guid streamId,
        string? wolverineResource = null,
        string? martenResource = null);

    // Wait for an async projection to catch up to a sequence number, with
    // a tunable polling interval and timeout. Spec authors care about the
    // wait, not how it's implemented.
    Task WaitForProjectionAsync<T>(
        this IStepContext context,
        Guid streamId,
        long minSequence,
        TimeSpan? timeout = null,
        string? martenResource = null);

    // Composite reset hook — invoke between scenarios to (a) clean Marten,
    // (b) drain in-flight Wolverine envelopes, (c) reset tracked-session
    // observers. Wired up automatically when both an IMartenResource and
    // IHostResource (with Wolverine) are registered.
    Task ResetCritterStackAsync(this IStepContext context);
}

public record AggregateExecution<T>(
    ITrackedSession Session,
    IReadOnlyList<IEvent> NewEvents,
    T? Aggregate);

Considerations

  • The Bobcat.Wolverine extensions already exist (see WolverineStepContextExtensions); CritterStack wires them together with the new Marten extensions instead of replacing either.
  • ExecuteAggregateCommandAsync<T> is the canonical "I sent a command, here's what changed" helper — it's what the BankAccountES, MeetingGroupMonolith, and ProjectManagement samples will all want. Worth getting right early.
  • Projection wait helper: poll IDocumentStore.Advanced.AllProjectionProgress() for the relevant projection's sequence; use exponential backoff or a configurable interval (default 50ms, max 5s).
  • Optional later: a "rebuild this projection now" helper for tests that want to assert against synchronously rebuilt state. Defer until a sample asks for it.

Tests / verification

  • Add at least one sample whose .feature exercises the aggregate-command flow end-to-end. BankAccountES is the natural fit (Given account opened, When deposit posted, Then balance is X).
  • Mirror Bobcat.Alba.Tests' shape — focus on the resource-lifecycle and step-context-lookup cases first; the integration scenarios live in the sample.

Out of scope

  • HTTP testing patterns (Bobcat.Alba already covers).
  • Bare-Marten or bare-Wolverine usage (the per-package extensions cover those).

Reference

  • Item Add Bobcat.Wolverine package with tracked session support #3 in CONTEXT.md "Next Steps" (line 153).
  • Existing analog: src/Bobcat.Wolverine/WolverineStepContextExtensions.cs (tracked-session helpers).
  • Critter Stack canonical pattern: see CritterWatch's ServiceSummaryProjection.RaiseSideEffects for an example of where a CritterStack-side test would assert the published event AND the resulting aggregate state.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions