Skip to content

Add store-agnostic projection dead-letter count read to IEventDatabase (Marten + Polecat) #356

Description

@jeremydmiller

Summary

Add a store-agnostic way to read projection dead-letter counts to JasperFx.Events, implemented in Marten and Polecat.

Motivation

JasperFx.Events 2.0 made SkipApplyErrors = true the default: a projection Apply() failure is recorded as a JasperFx.Events.Daemon.DeadLetterEvent and the shard keeps advancing rather than stalling. The accumulation of those rows is now the primary "this projection is unhealthy" signal.

As of 2.0.0-rc.2 the abstraction layer only lets you write dead letters:

  • IEventDatabase.StoreDeadLetterEventAsync(...)
  • ISubscriptionAgent.RecordDeadLetterEventAsync(...)
  • ISubscriptionController.RecordDeadLetterEventAsync(...)

There is no abstraction to read/count them. Consumers (e.g. CritterWatch's Wolverine.CritterWatch client package, which must depend only on JasperFx.Events) are forced to reflect into Marten's IDocumentStore and Query<DeadLetterEvent>() — Marten-only and reflection-based.

Proposal

Add a read method to JasperFx.Events.IEventDatabase, e.g.:

// per-shard
Task<long> CountDeadLetterEventsAsync(ShardName shard, CancellationToken token = default);
// or bulk, one row per (ProjectionName, ShardKey)
Task<IReadOnlyList<DeadLetterShardCount>> FetchDeadLetterCountsAsync(CancellationToken token = default);

(DeadLetterShardCountrecord(string ProjectionName, string ShardKey, long Count), keyed to align with ShardName.Name / ShardName.ShardKey as set by the DeadLetterEvent constructor.)

  • Marten: implement via the existing DeadLetterEvent document (Query<DeadLetterEvent>() grouped by ProjectionName/ShardName).
  • Polecat: implement the equivalent so Polecat-backed stores light up too.

Mirrors the existing IEventDatabase.AllProjectionProgress shape ("give me every progression row"), which both stores already implement.

Downstream

Unblocks JasperFx/CritterWatch removing its reflection-based ProjectionDeadLetterProbe stopgap (see the companion CritterWatch issue). CritterWatch will consume this via the graceful-no-op GetServices<IEventDatabase>() pattern.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions