feat(notification-outbox): add NotificationOutboxOptions and delivery adapter abstraction
This commit is contained in:
40
src/ScadaLink.NotificationOutbox/Delivery/DeliveryOutcome.cs
Normal file
40
src/ScadaLink.NotificationOutbox/Delivery/DeliveryOutcome.cs
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using ScadaLink.NotificationOutbox.Delivery;
|
||||
|
||||
namespace ScadaLink.NotificationOutbox.Tests.Delivery;
|
||||
|
||||
public class DeliveryOutcomeTests
|
||||
{
|
||||
[Fact]
|
||||
public void Success_SetsResolvedTargets_AndNoError()
|
||||
{
|
||||
var outcome = DeliveryOutcome.Success("targets");
|
||||
|
||||
Assert.Equal(DeliveryResult.Success, outcome.Result);
|
||||
Assert.Equal("targets", outcome.ResolvedTargets);
|
||||
Assert.Null(outcome.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transient_SetsError_AndNoResolvedTargets()
|
||||
{
|
||||
var outcome = DeliveryOutcome.Transient("e");
|
||||
|
||||
Assert.Equal(DeliveryResult.TransientFailure, outcome.Result);
|
||||
Assert.Equal("e", outcome.Error);
|
||||
Assert.Null(outcome.ResolvedTargets);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Permanent_SetsError_AndNoResolvedTargets()
|
||||
{
|
||||
var outcome = DeliveryOutcome.Permanent("e");
|
||||
|
||||
Assert.Equal(DeliveryResult.PermanentFailure, outcome.Result);
|
||||
Assert.Equal("e", outcome.Error);
|
||||
Assert.Null(outcome.ResolvedTargets);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace ScadaLink.NotificationOutbox.Tests;
|
||||
|
||||
public class NotificationOutboxOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void Defaults_AreExpectedValues()
|
||||
{
|
||||
var options = new NotificationOutboxOptions();
|
||||
|
||||
Assert.Equal(TimeSpan.FromSeconds(10), options.DispatchInterval);
|
||||
Assert.Equal(100, options.DispatchBatchSize);
|
||||
Assert.Equal(TimeSpan.FromMinutes(10), options.StuckAgeThreshold);
|
||||
Assert.Equal(TimeSpan.FromDays(365), options.TerminalRetention);
|
||||
Assert.Equal(TimeSpan.FromDays(1), options.PurgeInterval);
|
||||
Assert.Equal(TimeSpan.FromMinutes(1), options.DeliveredKpiWindow);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user