using System;
using Svrnty.CQRS.Events.Abstractions.EventStore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Svrnty.CQRS.Events.Abstractions;
using Svrnty.CQRS.Events.Abstractions.Projections;
namespace Svrnty.CQRS.Events.Projections;
///
/// Extension methods for registering event sourcing projections.
///
public static class ProjectionServiceCollectionExtensions
{
///
/// Adds projection infrastructure services to the service collection.
///
/// The service collection.
///
/// If true, uses in-memory checkpoint storage (for development/testing).
/// If false, requires PostgresProjectionCheckpointStore or custom implementation.
///
/// The service collection for chaining.
public static IServiceCollection AddProjections(
this IServiceCollection services,
bool useInMemoryCheckpoints = false)
{
// Register core services
services.TryAddSingleton();
services.TryAddSingleton();
// Register checkpoint store
if (useInMemoryCheckpoints)
{
services.TryAddSingleton();
}
// Register hosted service for auto-start projections
services.AddHostedService();
return services;
}
///
/// Registers a projection with the projection engine.
///
/// The type of the projection implementation.
/// The type of event the projection handles.
/// The service collection.
/// The unique name of the projection.
/// The name of the stream to consume from.
/// Optional configuration for projection options.
/// The service collection for chaining.
///
///
/// Example:
///
/// services.AddProjection<UserStatisticsProjection, UserRegisteredEvent>(
/// projectionName: "user-statistics",
/// streamName: "user-events",
/// configure: options =>
/// {
/// options.BatchSize = 100;
/// options.AutoStart = true;
/// });
///
///
///
public static IServiceCollection AddProjection(
this IServiceCollection services,
string projectionName,
string streamName,
Action? configure = null)
where TProjection : class, IProjection
where TEvent : ICorrelatedEvent
{
if (string.IsNullOrWhiteSpace(projectionName))
throw new ArgumentException("Projection name cannot be null or empty", nameof(projectionName));
if (string.IsNullOrWhiteSpace(streamName))
throw new ArgumentException("Stream name cannot be null or empty", nameof(streamName));
// Register projection implementation in DI
services.TryAddScoped();
// Build options
var options = new ProjectionOptions();
configure?.Invoke(options);
// Register projection definition
services.AddSingleton(sp =>
{
var registry = sp.GetRequiredService();
var definition = new ProjectionDefinition
{
ProjectionName = projectionName,
StreamName = streamName,
ProjectionType = typeof(TProjection),
EventType = typeof(TEvent),
Options = options,
Description = $"Projection '{projectionName}' consuming {typeof(TEvent).Name} from '{streamName}'"
};
registry.Register(definition);
return definition;
});
return services;
}
///
/// Registers a dynamic projection that can handle multiple event types.
///
/// The type of the projection implementation.
/// The service collection.
/// The unique name of the projection.
/// The name of the stream to consume from.
/// Optional configuration for projection options.
/// The service collection for chaining.
public static IServiceCollection AddDynamicProjection(
this IServiceCollection services,
string projectionName,
string streamName,
Action? configure = null)
where TProjection : class, IDynamicProjection
{
if (string.IsNullOrWhiteSpace(projectionName))
throw new ArgumentException("Projection name cannot be null or empty", nameof(projectionName));
if (string.IsNullOrWhiteSpace(streamName))
throw new ArgumentException("Stream name cannot be null or empty", nameof(streamName));
// Register projection implementation in DI
services.TryAddScoped();
// Build options
var options = new ProjectionOptions();
configure?.Invoke(options);
// Register projection definition
services.AddSingleton(sp =>
{
var registry = sp.GetRequiredService();
var definition = new ProjectionDefinition
{
ProjectionName = projectionName,
StreamName = streamName,
ProjectionType = typeof(TProjection),
EventType = null, // Dynamic projections don't have a specific event type
Options = options,
Description = $"Dynamic projection '{projectionName}' consuming from '{streamName}'"
};
registry.Register(definition);
return definition;
});
return services;
}
}