feat(notification-outbox): add NotificationOutboxOptions and delivery adapter abstraction

This commit is contained in:
Joseph Doherty
2026-05-19 01:20:49 -04:00
parent fb589bf1da
commit 8d52890245
5 changed files with 142 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
namespace ScadaLink.NotificationOutbox.Delivery;
/// <summary>
/// Classification of a single delivery attempt. Transient failures are eligible for
/// retry; permanent failures are terminal and not retried.
/// </summary>
public enum DeliveryResult
{
/// <summary>The notification was delivered successfully.</summary>
Success,
/// <summary>Delivery failed for a transient reason and may succeed on retry.</summary>
TransientFailure,
/// <summary>Delivery failed for a permanent reason and must not be retried.</summary>
PermanentFailure
}
/// <summary>
/// Result of a delivery attempt produced by an <see cref="INotificationDeliveryAdapter"/>.
/// </summary>
/// <param name="Result">The classification of the attempt.</param>
/// <param name="ResolvedTargets">
/// The concrete delivery targets used, snapshotted for audit. Set only on success.
/// </param>
/// <param name="Error">A human-readable failure description. Set only on failure.</param>
public record DeliveryOutcome(DeliveryResult Result, string? ResolvedTargets, string? Error)
{
/// <summary>Creates a successful outcome carrying the resolved delivery targets.</summary>
public static DeliveryOutcome Success(string resolvedTargets) =>
new(DeliveryResult.Success, resolvedTargets, null);
/// <summary>Creates a transient-failure outcome carrying an error description.</summary>
public static DeliveryOutcome Transient(string error) =>
new(DeliveryResult.TransientFailure, null, error);
/// <summary>Creates a permanent-failure outcome carrying an error description.</summary>
public static DeliveryOutcome Permanent(string error) =>
new(DeliveryResult.PermanentFailure, null, error);
}

View File

@@ -0,0 +1,23 @@
using ScadaLink.Commons.Entities.Notifications;
using ScadaLink.Commons.Types.Enums;
namespace ScadaLink.NotificationOutbox.Delivery;
/// <summary>
/// Channel-specific delivery strategy for outbox notifications. Each adapter handles
/// a single <see cref="NotificationType"/>; the outbox dispatcher selects the adapter
/// matching a notification's type.
/// </summary>
public interface INotificationDeliveryAdapter
{
/// <summary>The notification channel this adapter delivers.</summary>
NotificationType Type { get; }
/// <summary>
/// Attempts delivery of the given notification and reports the classified outcome.
/// </summary>
/// <param name="notification">The notification to deliver.</param>
/// <param name="cancellationToken">Token used to cancel the delivery attempt.</param>
/// <returns>The outcome of the delivery attempt.</returns>
Task<DeliveryOutcome> DeliverAsync(Notification notification, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,26 @@
namespace ScadaLink.NotificationOutbox;
/// <summary>
/// Configuration options for the Notification Outbox component: dispatch cadence,
/// batch sizing, stuck-message detection, terminal retention, and KPI windowing.
/// </summary>
public class NotificationOutboxOptions
{
/// <summary>Interval between dispatch sweeps that pick up pending notifications for delivery.</summary>
public TimeSpan DispatchInterval { get; set; } = TimeSpan.FromSeconds(10);
/// <summary>Maximum number of notifications claimed for delivery in a single dispatch sweep.</summary>
public int DispatchBatchSize { get; set; } = 100;
/// <summary>Age past which an in-progress notification is considered stuck and re-claimed.</summary>
public TimeSpan StuckAgeThreshold { get; set; } = TimeSpan.FromMinutes(10);
/// <summary>Retention period for notifications in a terminal state before they are purged.</summary>
public TimeSpan TerminalRetention { get; set; } = TimeSpan.FromDays(365);
/// <summary>Interval between background purge sweeps of terminal notifications.</summary>
public TimeSpan PurgeInterval { get; set; } = TimeSpan.FromDays(1);
/// <summary>Trailing window used to compute the delivered-notifications throughput KPI.</summary>
public TimeSpan DeliveredKpiWindow { get; set; } = TimeSpan.FromMinutes(1);
}