Ports, schema and failing tests for the subscriber half of S-19b-2, ahead of the implementation. The subscriber now listens on the `objecten` kanaal instead of `zaken`. An Objecten notification carries no record data — only the object URL — so the record is read back through the ACL (§8.1), and the zaak-shaped surface goes away: IsZaakCreated / IsZaakStatusSet / ZaakUrl / ZaakId and ToEntry's `Resource == "status"` mapping are replaced by IsRegisterRecordWritten + ObjectUrl. The notification log now holds the projected row itself (register id, status, reference), so a rebuild is a replay with no mapping rules and no upstream reads. The migration drops the old columns rather than renaming them — EF scaffolded renames that would have carried ZGW values into columns meaning something else — and empties both tables, since a pre-slice row is neither reprojectable nor re-derivable from the new source. Red: HandleAsync recognises a register write but does not yet read or project it, so the seven projection assertions fail on an empty store.
58 lines
3.0 KiB
C#
58 lines
3.0 KiB
C#
namespace EventSubscriber.Application;
|
|
|
|
/// <summary>
|
|
/// The durable log of notifications the subscriber has accepted. It is both the idempotency
|
|
/// guard (a replayed notification is recognised and dropped) and the rebuild source: the
|
|
/// projection is a derived artefact (PRD §8.4) regenerated by replaying this log, so a rebuild
|
|
/// needs no access to Objecten or ZGW (CLAUDE.md §8.1). Implemented in Infrastructure over Postgres.
|
|
/// </summary>
|
|
public interface INotificationLog
|
|
{
|
|
/// <summary>
|
|
/// Record a notification. Returns <c>true</c> if it was newly recorded, <c>false</c> if an
|
|
/// entry with the same key already existed (a duplicate delivery). Must be atomic so that
|
|
/// concurrent duplicate deliveries cannot both observe <c>true</c>.
|
|
/// </summary>
|
|
Task<bool> TryRecordAsync(RecordedNotification notification, CancellationToken ct = default);
|
|
|
|
/// <summary>Every accepted notification, for rebuilding the projection.</summary>
|
|
Task<IReadOnlyList<RecordedNotification>> AllAsync(CancellationToken ct = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// An accepted notification, retaining exactly the projection row it produced — so a rebuild
|
|
/// reproduces the row by replaying the log, without re-reading Objecten (S-19b-2, ADR-0030).
|
|
/// </summary>
|
|
public sealed record RecordedNotification(string Key, string RegisterId, string Status, string? Reference);
|
|
|
|
/// <summary>
|
|
/// Port to the Anti-Corruption Layer. An Objecten notification carries only the object URL, so the
|
|
/// subscriber reads the register record back through the ACL — the only code that may talk to
|
|
/// Objecten (§8.1, ADR-0028) — rather than reading Objecten itself.
|
|
/// </summary>
|
|
public interface IAclClient
|
|
{
|
|
/// <summary>The register record the object at <paramref name="objectUrl"/> holds, or
|
|
/// <c>null</c> if it holds none — the object may be gone by the time a redelivered
|
|
/// notification is handled, which is not an error (§8.6).</summary>
|
|
Task<RegisterRecord?> GetRegisterRecordAsync(Uri objectUrl, CancellationToken ct = default);
|
|
}
|
|
|
|
/// <summary>The public-safe register record as the ACL returns it — the RegisterRecord objecttype's
|
|
/// schema (ADR-0027). No bsn, no name: the register is world-readable.</summary>
|
|
public sealed record RegisterRecord(string Id, string Status, string? Reference);
|
|
|
|
/// <summary>The read projection store. Owned by the projection bounded context (ADR-0008); the
|
|
/// subscriber writes to it and the projection-api reads it.</summary>
|
|
public interface IProjectionStore
|
|
{
|
|
/// <summary>Insert or update the row for <see cref="RegisterEntry.Id"/>. Idempotent on the id.</summary>
|
|
Task UpsertAsync(RegisterEntry entry, CancellationToken ct = default);
|
|
|
|
/// <summary>Remove every row — the first step of a rebuild.</summary>
|
|
Task ClearAsync(CancellationToken ct = default);
|
|
|
|
/// <summary>Every projection row (used by tests and the rebuild verification).</summary>
|
|
Task<IReadOnlyList<RegisterEntry>> AllAsync(CancellationToken ct = default);
|
|
}
|