165 lines
6.2 KiB
C#
165 lines
6.2 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Extension methods for registering event sourcing projections.
|
|
/// </summary>
|
|
public static class ProjectionServiceCollectionExtensions
|
|
{
|
|
/// <summary>
|
|
/// Adds projection infrastructure services to the service collection.
|
|
/// </summary>
|
|
/// <param name="services">The service collection.</param>
|
|
/// <param name="useInMemoryCheckpoints">
|
|
/// If true, uses in-memory checkpoint storage (for development/testing).
|
|
/// If false, requires PostgresProjectionCheckpointStore or custom implementation.
|
|
/// </param>
|
|
/// <returns>The service collection for chaining.</returns>
|
|
public static IServiceCollection AddProjections(
|
|
this IServiceCollection services,
|
|
bool useInMemoryCheckpoints = false)
|
|
{
|
|
// Register core services
|
|
services.TryAddSingleton<IProjectionRegistry, ProjectionRegistry>();
|
|
services.TryAddSingleton<IProjectionEngine, ProjectionEngine>();
|
|
|
|
// Register checkpoint store
|
|
if (useInMemoryCheckpoints)
|
|
{
|
|
services.TryAddSingleton<IProjectionCheckpointStore, InMemoryProjectionCheckpointStore>();
|
|
}
|
|
|
|
// Register hosted service for auto-start projections
|
|
services.AddHostedService<ProjectionHostedService>();
|
|
|
|
return services;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a projection with the projection engine.
|
|
/// </summary>
|
|
/// <typeparam name="TProjection">The type of the projection implementation.</typeparam>
|
|
/// <typeparam name="TEvent">The type of event the projection handles.</typeparam>
|
|
/// <param name="services">The service collection.</param>
|
|
/// <param name="projectionName">The unique name of the projection.</param>
|
|
/// <param name="streamName">The name of the stream to consume from.</param>
|
|
/// <param name="configure">Optional configuration for projection options.</param>
|
|
/// <returns>The service collection for chaining.</returns>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <strong>Example:</strong>
|
|
/// <code>
|
|
/// services.AddProjection<UserStatisticsProjection, UserRegisteredEvent>(
|
|
/// projectionName: "user-statistics",
|
|
/// streamName: "user-events",
|
|
/// configure: options =>
|
|
/// {
|
|
/// options.BatchSize = 100;
|
|
/// options.AutoStart = true;
|
|
/// });
|
|
/// </code>
|
|
/// </para>
|
|
/// </remarks>
|
|
public static IServiceCollection AddProjection<TProjection, TEvent>(
|
|
this IServiceCollection services,
|
|
string projectionName,
|
|
string streamName,
|
|
Action<ProjectionOptions>? configure = null)
|
|
where TProjection : class, IProjection<TEvent>
|
|
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<TProjection>();
|
|
|
|
// Build options
|
|
var options = new ProjectionOptions();
|
|
configure?.Invoke(options);
|
|
|
|
// Register projection definition
|
|
services.AddSingleton(sp =>
|
|
{
|
|
var registry = sp.GetRequiredService<IProjectionRegistry>();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a dynamic projection that can handle multiple event types.
|
|
/// </summary>
|
|
/// <typeparam name="TProjection">The type of the projection implementation.</typeparam>
|
|
/// <param name="services">The service collection.</param>
|
|
/// <param name="projectionName">The unique name of the projection.</param>
|
|
/// <param name="streamName">The name of the stream to consume from.</param>
|
|
/// <param name="configure">Optional configuration for projection options.</param>
|
|
/// <returns>The service collection for chaining.</returns>
|
|
public static IServiceCollection AddDynamicProjection<TProjection>(
|
|
this IServiceCollection services,
|
|
string projectionName,
|
|
string streamName,
|
|
Action<ProjectionOptions>? 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<TProjection>();
|
|
|
|
// Build options
|
|
var options = new ProjectionOptions();
|
|
configure?.Invoke(options);
|
|
|
|
// Register projection definition
|
|
services.AddSingleton(sp =>
|
|
{
|
|
var registry = sp.GetRequiredService<IProjectionRegistry>();
|
|
|
|
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;
|
|
}
|
|
}
|