feat(notification-outbox): add AddNotificationOutbox DI registration

This commit is contained in:
Joseph Doherty
2026-05-19 02:07:29 -04:00
parent 517437b0d9
commit 703cb2d392
8 changed files with 218 additions and 45 deletions
@@ -0,0 +1,49 @@
using Microsoft.Extensions.DependencyInjection;
using ScadaLink.NotificationOutbox.Delivery;
namespace ScadaLink.NotificationOutbox;
/// <summary>
/// DI registration for the Notification Outbox component: binds
/// <see cref="NotificationOutboxOptions"/> and registers the channel delivery adapters.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>Configuration section bound to <see cref="NotificationOutboxOptions"/>.</summary>
public const string OptionsSection = "ScadaLink:NotificationOutbox";
/// <summary>
/// Registers the Notification Outbox services: the <see cref="NotificationOutboxOptions"/>
/// binding and the channel delivery adapters.
///
/// This extension covers only the outbox-specific registrations. The
/// <see cref="EmailNotificationDeliveryAdapter"/> reuses the
/// <see cref="ScadaLink.NotificationService"/> SMTP machinery —
/// <c>Func&lt;ISmtpClientWrapper&gt;</c>, <c>OAuth2TokenService</c> and
/// <c>NotificationOptions</c> — so the caller (the Host on the central node) must also
/// call <c>AddNotificationService()</c>. Re-registering those services here would
/// duplicate them; relying on <c>AddNotificationService</c> keeps a single source of truth.
///
/// <see cref="EmailNotificationDeliveryAdapter"/> is registered <em>scoped</em> because it
/// takes a scoped <see cref="ScadaLink.Commons.Interfaces.Repositories.INotificationRepository"/>
/// directly. The <see cref="NotificationOutboxActor"/> resolves the adapters from a fresh
/// scope per dispatch sweep rather than holding them, so no scoped adapter is captured by
/// the singleton actor.
/// </summary>
public static IServiceCollection AddNotificationOutbox(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.AddOptions<NotificationOutboxOptions>()
.BindConfiguration(OptionsSection);
// Scoped: the adapter holds a scoped INotificationRepository. Registered both under
// the interface (so the dispatch sweep can enumerate every channel adapter) and as
// the concrete type (so callers and tests can resolve it directly).
services.AddScoped<EmailNotificationDeliveryAdapter>();
services.AddScoped<INotificationDeliveryAdapter>(
sp => sp.GetRequiredService<EmailNotificationDeliveryAdapter>());
return services;
}
}