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.
38 lines
1.8 KiB
C#
38 lines
1.8 KiB
C#
namespace EventSubscriber.Application;
|
|
|
|
/// <summary>
|
|
/// Projects inbound NRC notifications into the read projection. Tolerates duplicate and
|
|
/// out-of-order deliveries (CLAUDE.md §8.6): the notification log dedups, and the projection
|
|
/// upsert is idempotent on the register id. Rebuilds the projection by replaying the log.
|
|
/// </summary>
|
|
public sealed class NotificationProjector(INotificationLog log, IProjectionStore store, IAclClient acl)
|
|
{
|
|
/// <summary>Handle one inbound notification. Reacts to a register record being written to
|
|
/// Objecten (S-19b-2, ADR-0030) and ignores everything else.</summary>
|
|
public async Task HandleAsync(Notification notification, CancellationToken ct = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(notification);
|
|
|
|
if (!notification.IsRegisterRecordWritten)
|
|
return;
|
|
|
|
// S-19b-2: reading the record back through the ACL and projecting it lands with the
|
|
// implementation; today nothing reaches the store.
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>Rebuild the projection from the durable notification log (PRD §8.4).</summary>
|
|
public async Task RebuildAsync(CancellationToken ct = default)
|
|
{
|
|
await store.ClearAsync(ct);
|
|
foreach (var recorded in await log.AllAsync(ct))
|
|
await store.UpsertAsync(ToEntry(recorded), ct);
|
|
}
|
|
|
|
/// <summary>The projection row for an accepted notification. The log already holds exactly the
|
|
/// row's fields, so a rebuild needs no mapping rules and no upstream reads. bsn/naam stay
|
|
/// deferred — the register record is public-safe by construction (ADR-0027).</summary>
|
|
private static RegisterEntry ToEntry(RecordedNotification recorded)
|
|
=> new(recorded.RegisterId, recorded.Status, recorded.Reference);
|
|
}
|