Skip to content

Register IProjectionCoordinator alias from store-specific interfaces (Marten + Polecat) #430

Description

@jeremydmiller

Goal

Allow consumers of JasperFx.Events to resolve JasperFx.Events.Daemon.IProjectionCoordinator directly from DI without needing to know which event store provider is hosting the application.

Current state

Both Marten and Polecat register their store-specific subtype, not the JasperFx base interface:

// Marten/Marten/MartenServiceCollectionExtensions.cs:755
Services.AddSingleton<Marten.Events.Daemon.Coordination.IProjectionCoordinator, ProjectionCoordinator>();

// Polecat/PolecatConfigurationExpression.cs:86
Services.AddSingleton<Polecat.Events.Daemon.Coordination.IProjectionCoordinator>(sp => ...);

Both store-specific interfaces inherit from JasperFx.Events.Daemon.IProjectionCoordinator, but the DI container only resolves the exact registered type. So:

services.GetService<JasperFx.Events.Daemon.IProjectionCoordinator>()  // null on a stock Marten OR Polecat host

Impact

Wolverine.CritterWatch (the cross-store monitoring client) wants to resolve IProjectionCoordinator once and not branch on store. It currently has to walk every registered IHostedService and OfType<IProjectionCoordinator>() — works, but it's awkward and depends on the implementation detail that both coordinators are also registered as IHostedService.

See JasperFx/CritterWatch#310 and the regression test at https://github.com/JasperFx/CritterWatch/blob/main/src/Tests/Integration/subscription_rewind_polecat.cs that pins this exact gap (the test asserts the direct lookup is null on both stores).

Proposed fix

In both MartenServiceCollectionExtensions.AddAsyncDaemon and PolecatConfigurationExpression.AddProjectionCoordinator, add a second AddSingleton factory that delegates to the store-specific registration:

// Marten:
Services.AddSingleton<JasperFx.Events.Daemon.IProjectionCoordinator>(sp =>
    sp.GetRequiredService<Marten.Events.Daemon.Coordination.IProjectionCoordinator>());

// Polecat:
Services.AddSingleton<JasperFx.Events.Daemon.IProjectionCoordinator>(sp =>
    sp.GetRequiredService<Polecat.Events.Daemon.Coordination.IProjectionCoordinator>());

Singleton-factory-delegating preserves the single-instance invariant — both keys resolve to the same coordinator.

Once this lands across the Marten + Polecat versions CritterWatch pins, the Wolverine.CritterWatch handler can collapse the walk back to a single GetService<IProjectionCoordinator>() call. The repeating-gap regression test on the CritterWatch side will fail on the next bump and remind us to flip the handler.

Scope

Two-line change in each of Marten and Polecat. Coordinated release across the family (CritterWatch follow-up bumps the package pins and simplifies the handler).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions