From f56d97bf24ad3d3e714c847810c2edcdd37a6e2a Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 14 Nov 2025 12:57:00 +0100 Subject: [PATCH 1/2] Use .NET 10 GA --- .github/workflows/preview.yml | 6 ++--- .github/workflows/publish.yml | 15 +++++++++++- .github/workflows/pull-request.yml | 6 ----- Directory.Packages.props | 7 +++--- .../Registrations/SubscriptionBuilder.cs | 2 +- .../Eventuous.Postgresql.csproj | 23 ++++++++----------- .../Eventuous.Sql.Base.csproj | 4 ++-- .../Eventuous.SqlServer.csproj | 3 --- 8 files changed, 34 insertions(+), 32 deletions(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 504b1587..772670d8 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -26,12 +26,12 @@ jobs: fetch-depth: 0 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '9.0.x' + dotnet-version: '10.0.x' - name: Run tests - run: dotnet test --framework net9.0 + run: dotnet test --framework net10.0 - name: Publish test results uses: EnricoMi/publish-unit-test-result-action/linux@v2 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3f172148..3b1d7a39 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,9 +25,22 @@ jobs: uses: actions/checkout@v5 with: fetch-depth: 0 + - + name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 10.0 + - + name: Login to Docker Hub + if: ${{ github.event.pull_request.head.repo.fork == false }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_TOKEN }} - name: Run tests - run: dotnet test --framework net9.0 + run: dotnet test --framework net10.0 - name: Publish test results uses: EnricoMi/publish-unit-test-result-action/linux@v2 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index fa76ff4d..889aa2eb 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -37,13 +37,7 @@ jobs: dotnet-version: | 8.0 9.0 - - - name: Setup .NET 10 - uses: actions/setup-dotnet@v5 - with: - dotnet-version: | 10.0 - dotnet-quality: 'preview' - name: Login to Docker Hub if: ${{ github.event.pull_request.head.repo.fork == false }} diff --git a/Directory.Packages.props b/Directory.Packages.props index 5bd2b3ee..9ea0f7a0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,8 +3,8 @@ true - 10.0.0-rc.2.25502.107 - 10.0.0-rc.2.25502.107 + 10.0.0 + 10.0.0 9.0.10 @@ -37,8 +37,9 @@ - + + diff --git a/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilder.cs b/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilder.cs index a04f40a7..37b0c0d5 100644 --- a/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilder.cs +++ b/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilder.cs @@ -232,7 +232,7 @@ IMessageConsumer ResolveDefaultConsumer(IServiceProvider sp) { /// Resolves and builds the subscription instance of type . /// Applies tracing and consumer filters to the consume pipe when diagnostics are enabled, /// resolves the configured consumer, and creates the subscription using options keyed by - /// . + /// SubscriptionId. /// /// Service provider used to resolve dependencies /// The resolved and configured subscription instance diff --git a/src/Postgres/src/Eventuous.Postgresql/Eventuous.Postgresql.csproj b/src/Postgres/src/Eventuous.Postgresql/Eventuous.Postgresql.csproj index 36021140..7d3ed25c 100644 --- a/src/Postgres/src/Eventuous.Postgresql/Eventuous.Postgresql.csproj +++ b/src/Postgres/src/Eventuous.Postgresql/Eventuous.Postgresql.csproj @@ -3,26 +3,23 @@ - + - + - - - - - - - - - - - + + + + + + + + diff --git a/src/Relational/src/Eventuous.Sql.Base/Eventuous.Sql.Base.csproj b/src/Relational/src/Eventuous.Sql.Base/Eventuous.Sql.Base.csproj index 8ebeb15d..b5c01e2c 100644 --- a/src/Relational/src/Eventuous.Sql.Base/Eventuous.Sql.Base.csproj +++ b/src/Relational/src/Eventuous.Sql.Base/Eventuous.Sql.Base.csproj @@ -19,7 +19,7 @@ - - + + diff --git a/src/SqlServer/src/Eventuous.SqlServer/Eventuous.SqlServer.csproj b/src/SqlServer/src/Eventuous.SqlServer/Eventuous.SqlServer.csproj index 1ad690fb..8ba83d74 100644 --- a/src/SqlServer/src/Eventuous.SqlServer/Eventuous.SqlServer.csproj +++ b/src/SqlServer/src/Eventuous.SqlServer/Eventuous.SqlServer.csproj @@ -10,9 +10,6 @@ - - - From cc664d1af43e501f753052c353701d7f5d5c38e4 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Sat, 15 Nov 2025 16:19:04 +0100 Subject: [PATCH 2/2] Code cleanup --- Directory.Packages.props | 3 - samples/Directory.Build.props | 2 +- .../Bookings.Domain/Bookings/BookingState.cs | 12 +- .../Application/CommandService.cs | 2 +- .../Bookings.Payments.csproj | 2 +- .../Bookings.Payments/Integration/Payments.cs | 6 +- .../esdb/Bookings.Payments/Registrations.cs | 66 ++- .../Queries/MyBookingsProjection.cs | 2 +- samples/esdb/Bookings/Bookings.csproj | 2 +- samples/esdb/Bookings/Infrastructure/Mongo.cs | 2 +- samples/esdb/Bookings/Registrations.cs | 2 +- .../Bookings.Payments/Integration/Payments.cs | 2 +- .../Bookings.Payments/Registrations.cs | 2 +- .../Queries/MyBookingsProjection.cs | 2 +- samples/postgres/Bookings/Registrations.cs | 2 +- .../TypeMappingsGenerator.cs | 11 +- .../ConsumeContextConverterGenerator.cs | 2 +- .../AggregateService/CommandHandlerBuilder.cs | 2 +- .../CommandHandlerBuilder.cs | 4 +- .../ActivityExtensions.cs | 92 +-- .../MetadataExtensions.cs | 38 +- .../AggregatePersistenceExtensions.cs | 308 +++++----- .../AggregateStoreExtensions.cs | 85 +-- .../StateStore/StateStoreFunctions.cs | 101 ++-- .../Eventuous.Producers/ProducerExtensions.cs | 2 +- .../RegistrationExtensions.cs | 92 +-- .../Meta/MetadataExtensions.cs | 49 +- .../TypeMap/TypeMapperExtensions.cs | 75 +-- .../Channels/ChannelExtensions.cs | 78 +-- .../Context/ContextResultExtensions.cs | 145 +++-- .../EventSubscriptionWithCheckpoint.cs | 2 +- .../Logging/CheckpointLogging.cs | 36 +- .../Logging/SubscriptionLogging.cs | 164 +++-- .../SubscriptionBuilderExtensions.cs | 182 +++--- .../SubscriptionRegistrationExtensions.cs | 20 +- .../Fixtures/Helpers.cs | 39 +- .../Analyzer_Ev001_Tests.cs | 2 +- .../Fixtures/SubscriptionExtensions.cs | 24 +- .../Fixtures/TestEventHandler.cs | 2 +- .../SequenceTests.cs | 4 +- .../MeterProviderBuilderExtensions.cs | 71 +-- .../TracerProviderBuilderExtensions.cs | 2 +- .../MetricsTests.cs | 14 +- .../src/ElasticPlayground/ConfigureElastic.cs | 16 +- .../ElasticPlayground.csproj | 2 +- .../src/ElasticPlayground/MiscExtensions.cs | 5 +- .../src/Eventuous.Spyglass/Accessor.cs | 8 +- .../src/Eventuous.Spyglass/InsidePeek.cs | 4 +- .../Diagnostics.cs | 13 +- ...us.Extensions.AspNetCore.Generators.csproj | 7 +- .../HttpCommandStateMismatchAnalyzer.cs | 21 +- .../Http/CommandMappingRegistry.cs | 2 +- .../Http/HttpCommandMapping.cs | 187 +++--- .../Http/HttpCommandMappingExt.cs | 67 ++- .../Http/ResultExtensions.cs | 66 ++- .../Http/RouteHandlerBuilderExt.cs | 54 +- .../Registrations/AggregateFactory.cs | 61 +- .../Registrations/AggregateStore.cs | 95 +-- .../Registrations/Services.cs | 77 +-- .../Registrations/StoreWithArchive.cs | 72 ++- .../Registrations/Stores.cs | 271 +++++---- .../SubscriptionBuilderExtensions.cs | 51 +- .../AnalyzerTestShared.cs | 8 +- .../RouteMismatchTests.cs | 3 +- .../StateTypeMismatchTests.cs | 3 +- .../AggregateCommandsTests.cs | 2 +- .../Eventuous.Gateway/GatewayMetaHelper.cs | 34 +- .../Registrations/GatewayRegistrations.cs | 268 +++++---- .../src/Eventuous.Kafka/MetadataExtensions.cs | 24 +- .../Eventuous.KurrentDB.csproj | 3 - ...eOptions.cs => KurrentDBProduceOptions.cs} | 4 +- ...rentDbProducer.cs => KurrentDBProducer.cs} | 14 +- .../StreamRevisionExtensions.cs | 7 - .../Subscriptions/AllStreamSubscription.cs | 4 +- ...cs => KurrentDBCatchUpSubscriptionBase.cs} | 4 +- ...DbExtensions.cs => KurrentDBExtensions.cs} | 2 +- .../{EsdbMappings.cs => KurrentDBMappings.cs} | 4 +- .../Options/CatchUpSubscriptionOptions.cs | 2 +- ...ons.cs => KurrentDBSubscriptionOptions.cs} | 2 +- ...entDBSubscriptionWithCheckpointOptions.cs} | 2 +- .../Options/PersistentSubscriptionOptions.cs | 2 +- .../PersistentSubscriptionBase.cs | 2 +- .../Subscriptions/StreamSubscription.cs | 4 +- .../Eventuous.Tests.KurrentDB.csproj | 3 - .../Metrics/MetricsFixture.cs | 2 +- .../Subscriptions/CustomDependenciesTests.cs | 2 +- .../Fixtures/CatchUpSubscriptionFixture.cs | 2 +- .../Fixtures/LegacySubscriptionFixture.cs | 3 +- .../Fixtures/PersistentSubscriptionFixture.cs | 2 +- ...PublishAndSubscribeManyPartitionedTests.cs | 2 +- .../StreamSubscriptionWithLinksTests.cs | 2 +- .../SubscriptionIgnoredMessagesTests.cs | 4 +- .../Tools/MongoCollectionExtensions.cs | 560 +++++++++--------- .../Tools/MongoCollectionName.cs | 6 +- .../Tools/MongoDatabaseExtensions.cs | 544 ++++++++--------- .../Extensions/PostgresExtensions.cs | 16 +- .../Extensions/RegistrationExtensions.cs | 219 ++++--- .../Subscriptions/PostgresCheckpointStore.cs | 2 +- .../Subscriptions/PostgresSubscriptionBase.cs | 2 +- .../Projections/ProjectorTests.cs | 2 +- .../Subscriptions/TombstonesCreationTest.cs | 2 +- .../Subscriptions/RedisCheckpointStore.cs | 2 +- .../Subscriptions/RedisSubscriptionBase.cs | 4 +- .../Fixtures/SubscriptionFixture.cs | 2 +- .../Eventuous.Tests.Redis/Store/Helpers.cs | 29 +- .../Extensions/RegistrationExtensions.cs | 125 ++-- .../Extensions/SqlExtensions.cs | 84 +-- .../AggregateFactoryExtensions.cs | 49 +- test/Eventuous.Sut.Domain/BookingState.cs | 2 +- .../IntToTimespan.cs | 7 +- .../Logging/LoggingExtensions.cs | 29 +- 111 files changed, 2439 insertions(+), 2501 deletions(-) rename src/KurrentDB/src/Eventuous.KurrentDB/Producers/{KurrentDbProduceOptions.cs => KurrentDBProduceOptions.cs} (89%) rename src/KurrentDB/src/Eventuous.KurrentDB/Producers/{KurrentDbProducer.cs => KurrentDBProducer.cs} (90%) rename src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/{EventStoreCatchUpSubscriptionBase.cs => KurrentDBCatchUpSubscriptionBase.cs} (92%) rename src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/{KurrentDbExtensions.cs => KurrentDBExtensions.cs} (96%) rename src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/{EsdbMappings.cs => KurrentDBMappings.cs} (87%) rename src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/{EventStoreSubscriptionOptions.cs => KurrentDBSubscriptionOptions.cs} (86%) rename src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/{EventStoreSubscriptionWithCheckpointOptions.cs => KurrentDBSubscriptionWithCheckpointOptions.cs} (82%) diff --git a/Directory.Packages.props b/Directory.Packages.props index c14d4ed8..dfcedb82 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -38,12 +38,9 @@ - - - diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index a21a2e27..d99b79eb 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -1,6 +1,6 @@ - net9.0 + net10.0 enable enable preview diff --git a/samples/esdb/Bookings.Domain/Bookings/BookingState.cs b/samples/esdb/Bookings.Domain/Bookings/BookingState.cs index 15fbda34..88680376 100644 --- a/samples/esdb/Bookings.Domain/Bookings/BookingState.cs +++ b/samples/esdb/Bookings.Domain/Bookings/BookingState.cs @@ -28,17 +28,17 @@ public BookingState() { static BookingState HandlePayment(BookingState state, V1.PaymentRecorded e) => state with { - Outstanding = new Money { Amount = e.Outstanding, Currency = e.Currency }, - Payments = state.Payments.Add(new PaymentRecord(e.PaymentId, new Money(e.PaidAmount, e.Currency))) + Outstanding = new() { Amount = e.Outstanding, Currency = e.Currency }, + Payments = state.Payments.Add(new(e.PaymentId, new(e.PaidAmount, e.Currency))) }; static BookingState HandleBooked(BookingState state, V1.RoomBooked booked) => state with { - RoomId = new RoomId(booked.RoomId), - Period = new StayPeriod(booked.CheckInDate, booked.CheckOutDate), + RoomId = new(booked.RoomId), + Period = new(booked.CheckInDate, booked.CheckOutDate), GuestId = booked.GuestId, - Price = new Money { Amount = booked.BookingPrice, Currency = booked.Currency }, - Outstanding = new Money { Amount = booked.OutstandingAmount, Currency = booked.Currency } + Price = new() { Amount = booked.BookingPrice, Currency = booked.Currency }, + Outstanding = new() { Amount = booked.OutstandingAmount, Currency = booked.Currency } }; } diff --git a/samples/esdb/Bookings.Payments/Application/CommandService.cs b/samples/esdb/Bookings.Payments/Application/CommandService.cs index 12e49a6e..7f913659 100644 --- a/samples/esdb/Bookings.Payments/Application/CommandService.cs +++ b/samples/esdb/Bookings.Payments/Application/CommandService.cs @@ -10,7 +10,7 @@ public CommandService(IEventStore store) : base(store) { On() .InState(ExpectedState.New) .GetId(cmd => new(cmd.PaymentId)) - .Act((payment, cmd) => payment.ProcessPayment(cmd.BookingId, new Money(cmd.Amount, cmd.Currency), cmd.Method, cmd.Provider)); + .Act((payment, cmd) => payment.ProcessPayment(cmd.BookingId, new(cmd.Amount, cmd.Currency), cmd.Method, cmd.Provider)); } } diff --git a/samples/esdb/Bookings.Payments/Bookings.Payments.csproj b/samples/esdb/Bookings.Payments/Bookings.Payments.csproj index a604b03a..42d100cf 100644 --- a/samples/esdb/Bookings.Payments/Bookings.Payments.csproj +++ b/samples/esdb/Bookings.Payments/Bookings.Payments.csproj @@ -24,7 +24,7 @@ - + diff --git a/samples/esdb/Bookings.Payments/Integration/Payments.cs b/samples/esdb/Bookings.Payments/Integration/Payments.cs index 737f0573..8b724e32 100644 --- a/samples/esdb/Bookings.Payments/Integration/Payments.cs +++ b/samples/esdb/Bookings.Payments/Integration/Payments.cs @@ -11,16 +11,16 @@ namespace Bookings.Payments.Integration; public static class PaymentsGateway { static readonly StreamName Stream = new("PaymentsIntegration"); - public static ValueTask[]> Transform(IMessageConsumeContext original) { + public static ValueTask[]> Transform(IMessageConsumeContext original) { var result = original.Message is PaymentEvents.PaymentRecorded evt - ? new GatewayMessage( + ? new GatewayMessage( Stream, new BookingPaymentRecorded(original.Stream.GetId(), evt.BookingId, evt.Amount, evt.Currency), new(), new() ) : null; - GatewayMessage[] gatewayMessages = result != null ? [result] : []; + GatewayMessage[] gatewayMessages = result != null ? [result] : []; return ValueTask.FromResult(gatewayMessages); } } diff --git a/samples/esdb/Bookings.Payments/Registrations.cs b/samples/esdb/Bookings.Payments/Registrations.cs index 5110a8ab..ce4d6766 100644 --- a/samples/esdb/Bookings.Payments/Registrations.cs +++ b/samples/esdb/Bookings.Payments/Registrations.cs @@ -14,40 +14,42 @@ namespace Bookings.Payments; public static class Registrations { - public static void AddServices(this IServiceCollection services, IConfiguration configuration) { - services.AddKurrentDBClient(configuration["EventStore:ConnectionString"]!); - services.AddEventStore(); - services.AddCommandService(); - services.AddSingleton(Mongo.ConfigureMongo(configuration)); - services.AddCheckpointStore(); - services.AddProducer(); + extension(IServiceCollection services) { + public void AddServices(IConfiguration configuration) { + services.AddKurrentDBClient(configuration["EventStore:ConnectionString"]!); + services.AddEventStore(); + services.AddCommandService(); + services.AddSingleton(Mongo.ConfigureMongo(configuration)); + services.AddCheckpointStore(); + services.AddProducer(); - services - .AddGateway( - "IntegrationSubscription", - PaymentsGateway.Transform - ); - } + services + .AddGateway( + "IntegrationSubscription", + PaymentsGateway.Transform + ); + } - public static void AddTelemetry(this IServiceCollection services) { - services.AddOpenTelemetry() - .WithMetrics( - builder => builder - .AddAspNetCoreInstrumentation() - .AddEventuous() - .AddEventuousSubscriptions() - .AddPrometheusExporter() - ); + public void AddTelemetry() { + services.AddOpenTelemetry() + .WithMetrics( + builder => builder + .AddAspNetCoreInstrumentation() + .AddEventuous() + .AddEventuousSubscriptions() + .AddPrometheusExporter() + ); - services.AddOpenTelemetry() - .WithTracing( - builder => builder - .AddAspNetCoreInstrumentation() - .AddGrpcClientInstrumentation() - .AddEventuousTracing() - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("payments")) - .SetSampler(new AlwaysOnSampler()) - .AddZipkinExporter() - ); + services.AddOpenTelemetry() + .WithTracing( + builder => builder + .AddAspNetCoreInstrumentation() + .AddGrpcClientInstrumentation() + .AddEventuousTracing() + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("payments")) + .SetSampler(new AlwaysOnSampler()) + .AddZipkinExporter() + ); + } } } diff --git a/samples/esdb/Bookings/Application/Queries/MyBookingsProjection.cs b/samples/esdb/Bookings/Application/Queries/MyBookingsProjection.cs index ee7c14cf..9301e77f 100644 --- a/samples/esdb/Bookings/Application/Queries/MyBookingsProjection.cs +++ b/samples/esdb/Bookings/Application/Queries/MyBookingsProjection.cs @@ -12,7 +12,7 @@ public MyBookingsProjection(IMongoDatabase database) : base(database) { .UpdateFromContext((ctx, update) => update.AddToSet( x => x.Bookings, - new MyBookings.Booking(ctx.Stream.GetId(), + new(ctx.Stream.GetId(), ctx.Message.CheckInDate, ctx.Message.CheckOutDate, ctx.Message.BookingPrice diff --git a/samples/esdb/Bookings/Bookings.csproj b/samples/esdb/Bookings/Bookings.csproj index 7403c67c..93ba8b39 100644 --- a/samples/esdb/Bookings/Bookings.csproj +++ b/samples/esdb/Bookings/Bookings.csproj @@ -25,7 +25,7 @@ - + diff --git a/samples/esdb/Bookings/Infrastructure/Mongo.cs b/samples/esdb/Bookings/Infrastructure/Mongo.cs index e54e029a..74a5e802 100644 --- a/samples/esdb/Bookings/Infrastructure/Mongo.cs +++ b/samples/esdb/Bookings/Infrastructure/Mongo.cs @@ -11,7 +11,7 @@ public static IMongoDatabase ConfigureMongo(IConfiguration configuration) { var settings = MongoClientSettings.FromConnectionString(config!.ConnectionString); if (config is { User: not null, Password: not null }) { - settings.Credential = new MongoCredential( + settings.Credential = new( null, new MongoInternalIdentity("admin", config.User), new PasswordEvidence(config.Password) diff --git a/samples/esdb/Bookings/Registrations.cs b/samples/esdb/Bookings/Registrations.cs index 46d6cf36..5bc7d77c 100644 --- a/samples/esdb/Bookings/Registrations.cs +++ b/samples/esdb/Bookings/Registrations.cs @@ -32,7 +32,7 @@ public void AddEventuous(IConfiguration configuration) { services.AddCommandService(); services.AddSingleton((_, _) => new(true)); - services.AddSingleton((from, currency) => new Money(from.Amount * 2, currency)); + services.AddSingleton((from, currency) => new(from.Amount * 2, currency)); services.AddSingleton(Mongo.ConfigureMongo(configuration)); diff --git a/samples/postgres/Bookings.Payments/Integration/Payments.cs b/samples/postgres/Bookings.Payments/Integration/Payments.cs index 6d9d703a..4ecaa041 100644 --- a/samples/postgres/Bookings.Payments/Integration/Payments.cs +++ b/samples/postgres/Bookings.Payments/Integration/Payments.cs @@ -16,7 +16,7 @@ public static ValueTask[]> Transform(IMes ? new GatewayMessage( Stream, new BookingPaymentRecorded(original.Stream.GetId(), evt.BookingId, evt.Amount, evt.Currency), - new Metadata(), + new(), ProduceOptions ) : null; diff --git a/samples/postgres/Bookings.Payments/Registrations.cs b/samples/postgres/Bookings.Payments/Registrations.cs index 3e420228..78a4d48f 100644 --- a/samples/postgres/Bookings.Payments/Registrations.cs +++ b/samples/postgres/Bookings.Payments/Registrations.cs @@ -13,7 +13,7 @@ namespace Bookings.Payments; public static class Registrations { public static void AddEventuous(this IServiceCollection services, IConfiguration configuration) { var connectionFactory = new ConnectionFactory { - Uri = new Uri(configuration["RabbitMq:ConnectionString"]!), + Uri = new(configuration["RabbitMq:ConnectionString"]!), DispatchConsumersAsync = true }; services.AddSingleton(connectionFactory); diff --git a/samples/postgres/Bookings/Application/Queries/MyBookingsProjection.cs b/samples/postgres/Bookings/Application/Queries/MyBookingsProjection.cs index ee7c14cf..9301e77f 100644 --- a/samples/postgres/Bookings/Application/Queries/MyBookingsProjection.cs +++ b/samples/postgres/Bookings/Application/Queries/MyBookingsProjection.cs @@ -12,7 +12,7 @@ public MyBookingsProjection(IMongoDatabase database) : base(database) { .UpdateFromContext((ctx, update) => update.AddToSet( x => x.Bookings, - new MyBookings.Booking(ctx.Stream.GetId(), + new(ctx.Stream.GetId(), ctx.Message.CheckInDate, ctx.Message.CheckOutDate, ctx.Message.BookingPrice diff --git a/samples/postgres/Bookings/Registrations.cs b/samples/postgres/Bookings/Registrations.cs index b13933f2..78a7040a 100644 --- a/samples/postgres/Bookings/Registrations.cs +++ b/samples/postgres/Bookings/Registrations.cs @@ -37,7 +37,7 @@ public static void AddEventuous(this IServiceCollection services, IConfiguration services.AddSingleton((_, _) => new(true)); services.AddSingleton( - (from, currency) => new Money(from.Amount * 2, currency) + (from, currency) => new(from.Amount * 2, currency) ); services.AddSingleton(Mongo.ConfigureMongo(configuration)); diff --git a/src/Core/gen/Eventuous.Shared.Generators/TypeMappingsGenerator.cs b/src/Core/gen/Eventuous.Shared.Generators/TypeMappingsGenerator.cs index be51e805..a428e14c 100644 --- a/src/Core/gen/Eventuous.Shared.Generators/TypeMappingsGenerator.cs +++ b/src/Core/gen/Eventuous.Shared.Generators/TypeMappingsGenerator.cs @@ -172,12 +172,11 @@ static void Generate(SourceProductionContext context, string assemblyName, Immut foreach (var m in distinct) { // Prefer passing the explicit name when we have it, so runtime reflection is avoided - if (!string.IsNullOrEmpty(m.EventTypeName)) { - sb.AppendLine($" mapper.AddType(typeof({m.FullyQualifiedType}), \"{EscapeString(m.EventTypeName)}\");"); - } - else { - sb.AppendLine($" mapper.AddType(typeof({m.FullyQualifiedType}));"); - } + sb.AppendLine( + !string.IsNullOrEmpty(m.EventTypeName) + ? $" mapper.AddType(typeof({m.FullyQualifiedType}), \"{EscapeString(m.EventTypeName)}\");" + : $" mapper.AddType(typeof({m.FullyQualifiedType}));" + ); } sb.AppendLine(" }"); diff --git a/src/Core/gen/Eventuous.Subscriptions.Generators/ConsumeContextConverterGenerator.cs b/src/Core/gen/Eventuous.Subscriptions.Generators/ConsumeContextConverterGenerator.cs index 822c6cda..194d9a0f 100644 --- a/src/Core/gen/Eventuous.Subscriptions.Generators/ConsumeContextConverterGenerator.cs +++ b/src/Core/gen/Eventuous.Subscriptions.Generators/ConsumeContextConverterGenerator.cs @@ -25,7 +25,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { static bool IsPotentialUsage(SyntaxNode node, CancellationToken _) { return node switch { - GenericNameSyntax { TypeArgumentList.Arguments.Count: 1 } g => g.Identifier.Text == "IMessageConsumeContext" || g.Identifier.Text == "On", + GenericNameSyntax { TypeArgumentList.Arguments.Count: 1 } g => g.Identifier.Text is "IMessageConsumeContext" or "On", // handle qualified names like Eventuous.Subscriptions.Context.IMessageConsumeContext QualifiedNameSyntax { Right: GenericNameSyntax { TypeArgumentList.Arguments.Count: 1 } g2 } => g2.Identifier.Text == "IMessageConsumeContext", // implicit: lambdas where parameter type is inferred to IMessageConsumeContext diff --git a/src/Core/src/Eventuous.Application/AggregateService/CommandHandlerBuilder.cs b/src/Core/src/Eventuous.Application/AggregateService/CommandHandlerBuilder.cs index 2deda727..26430764 100644 --- a/src/Core/src/Eventuous.Application/AggregateService/CommandHandlerBuilder.cs +++ b/src/Core/src/Eventuous.Application/AggregateService/CommandHandlerBuilder.cs @@ -205,7 +205,7 @@ IDefineAppendAmendment IDefineExecution. } IDefineExecution IDefineStore.ResolveStore(Func resolveStore) { - Ensure.NotNull(resolveStore, nameof(resolveStore)); + Ensure.NotNull(resolveStore); _reader = resolveStore; _writer = resolveStore; diff --git a/src/Core/src/Eventuous.Application/FunctionalService/CommandHandlerBuilder.cs b/src/Core/src/Eventuous.Application/FunctionalService/CommandHandlerBuilder.cs index 0cdf2983..6e9dada8 100644 --- a/src/Core/src/Eventuous.Application/FunctionalService/CommandHandlerBuilder.cs +++ b/src/Core/src/Eventuous.Application/FunctionalService/CommandHandlerBuilder.cs @@ -232,13 +232,13 @@ RegisteredHandler Build() { ); Func DefaultResolveWriter() { - ArgumentNullException.ThrowIfNull(writer, nameof(writer)); + ArgumentNullException.ThrowIfNull(writer); return _ => writer; } Func DefaultResolveReader() { - ArgumentNullException.ThrowIfNull(reader, nameof(reader)); + ArgumentNullException.ThrowIfNull(reader); return _ => reader; } diff --git a/src/Core/src/Eventuous.Diagnostics/ActivityExtensions.cs b/src/Core/src/Eventuous.Diagnostics/ActivityExtensions.cs index 25cac0cf..76d2cc70 100644 --- a/src/Core/src/Eventuous.Diagnostics/ActivityExtensions.cs +++ b/src/Core/src/Eventuous.Diagnostics/ActivityExtensions.cs @@ -4,60 +4,62 @@ namespace Eventuous.Diagnostics; public static class ActivityExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static string? GetParentTag(this Activity activity, string tag) - => activity.Parent?.Tags.FirstOrDefault(x => x.Key == tag).Value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Activity CopyParentTag(this Activity activity, string tag, string? parentTag = null) { - var value = activity.GetParentTag(parentTag ?? tag); - if (value != null) activity.SetTag(tag, value); - return activity; - } + extension(Activity activity) { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + string? GetParentTag(string tag) + => activity.Parent?.Tags.FirstOrDefault(x => x.Key == tag).Value; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Activity SetOrCopyParentTag(this Activity activity, string tag, string? value, string? parentTag = null) { - var val = value ?? activity.GetParentTag(parentTag ?? tag); - if (val != null) activity.SetTag(tag, val); - return activity; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Activity CopyParentTag(string tag, string? parentTag = null) { + var value = activity.GetParentTag(parentTag ?? tag); + if (value != null) activity.SetTag(tag, value); + return activity; + } - public static TracingMeta GetTracingData(this Activity activity) - => new(activity.TraceId.ToString(), activity.SpanId.ToString()); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Activity SetOrCopyParentTag(string tag, string? value, string? parentTag = null) { + var val = value ?? activity.GetParentTag(parentTag ?? tag); + if (val != null) activity.SetTag(tag, val); + return activity; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Activity SetActivityStatus(this Activity activity, ActivityStatus status) { - var (activityStatusCode, description, exception) = status; + public TracingMeta GetTracingData() + => new(activity.TraceId.ToString(), activity.SpanId.ToString()); - var statusCode = activityStatusCode switch { - ActivityStatusCode.Error => "ERROR", - ActivityStatusCode.Ok => "OK", - _ => "UNSET" - }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Activity SetActivityStatus(ActivityStatus status) { + var (activityStatusCode, description, exception) = status; - activity.SetStatus(activityStatusCode, description); - activity.SetTag(TelemetryTags.Otel.StatusCode, statusCode); - activity.SetTag(TelemetryTags.Otel.StatusDescription, description); + var statusCode = activityStatusCode switch { + ActivityStatusCode.Error => "ERROR", + ActivityStatusCode.Ok => "OK", + _ => "UNSET" + }; - return !activity.IsAllDataRequested ? activity : activity.SetException(exception); - } + activity.SetStatus(activityStatusCode, description); + activity.SetTag(TelemetryTags.Otel.StatusCode, statusCode); + activity.SetTag(TelemetryTags.Otel.StatusDescription, description); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static Activity SetException(this Activity activity, Exception? exception) { - if (exception == null) return activity; + return !activity.IsAllDataRequested ? activity : activity.SetException(exception); + } - var tags = new ActivityTagsCollection( - [ - new(TelemetryTags.Exception.Type, exception.GetType().Name), - new(TelemetryTags.Exception.Message, $"{exception.Message} {exception.InnerException?.Message}"), - new(TelemetryTags.Exception.Stacktrace, exception.StackTrace) - ] - ); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + Activity SetException(Exception? exception) { + if (exception == null) return activity; - foreach (var (key, value) in tags) { - activity.SetTag(key, value); - } + var tags = new ActivityTagsCollection( + [ + new(TelemetryTags.Exception.Type, exception.GetType().Name), + new(TelemetryTags.Exception.Message, $"{exception.Message} {exception.InnerException?.Message}"), + new(TelemetryTags.Exception.Stacktrace, exception.StackTrace) + ] + ); - return activity.AddEvent(new(TelemetryTags.Exception.EventName, DateTimeOffset.Now, tags)); + foreach (var (key, value) in tags) { + activity.SetTag(key, value); + } + + return activity.AddEvent(new(TelemetryTags.Exception.EventName, DateTimeOffset.Now, tags)); + } } } diff --git a/src/Core/src/Eventuous.Diagnostics/MetadataExtensions.cs b/src/Core/src/Eventuous.Diagnostics/MetadataExtensions.cs index 9fa6c478..ed473967 100644 --- a/src/Core/src/Eventuous.Diagnostics/MetadataExtensions.cs +++ b/src/Core/src/Eventuous.Diagnostics/MetadataExtensions.cs @@ -6,29 +6,31 @@ namespace Eventuous.Diagnostics; using static DiagnosticTags; public static class MetadataExtensions { - public static Metadata AddActivityTags(this Metadata metadata, Activity? activity) { - if (activity == null) return metadata; + extension(Metadata metadata) { + public Metadata AddActivityTags(Activity? activity) { + if (activity == null) return metadata; - var tags = activity.Tags.Where(x => x.Value != null && MetaMappings.TelemetryToInternalTagsMap.ContainsKey(x.Key)); + var tags = activity.Tags.Where(x => x.Value != null && MetaMappings.TelemetryToInternalTagsMap.ContainsKey(x.Key)); - foreach (var (key, value) in tags) { - metadata.With(MetaMappings.TelemetryToInternalTagsMap[key], value!); + foreach (var (key, value) in tags) { + metadata.With(MetaMappings.TelemetryToInternalTagsMap[key], value!); + } + + return metadata.AddTracingMeta(activity.GetTracingData()); } - return metadata.AddTracingMeta(activity.GetTracingData()); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + Metadata AddTracingMeta(TracingMeta tracingMeta) + => metadata.ContainsKey(TraceId) || tracingMeta.TraceId == EmptyId + ? metadata // don't override existing tracing data + : metadata + .AddNotNull(TraceId, tracingMeta.TraceId) + .AddNotNull(SpanId, tracingMeta.SpanId); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TracingMeta GetTracingMeta() + => new(metadata.GetString(TraceId), metadata.GetString(SpanId)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static Metadata AddTracingMeta(this Metadata metadata, TracingMeta tracingMeta) - => metadata.ContainsKey(TraceId) || tracingMeta.TraceId == EmptyId - ? metadata // don't override existing tracing data - : metadata - .AddNotNull(TraceId, tracingMeta.TraceId) - .AddNotNull(SpanId, tracingMeta.SpanId); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TracingMeta GetTracingMeta(this Metadata metadata) - => new(metadata.GetString(TraceId), metadata.GetString(SpanId)); - const string EmptyId = "0000000000000000"; } diff --git a/src/Core/src/Eventuous.Persistence/AggregateStore/AggregatePersistenceExtensions.cs b/src/Core/src/Eventuous.Persistence/AggregateStore/AggregatePersistenceExtensions.cs index 0e844ce5..c8e3143f 100644 --- a/src/Core/src/Eventuous.Persistence/AggregateStore/AggregatePersistenceExtensions.cs +++ b/src/Core/src/Eventuous.Persistence/AggregateStore/AggregatePersistenceExtensions.cs @@ -7,172 +7,168 @@ namespace Eventuous; using static Diagnostics.PersistenceEventSource; public static class AggregatePersistenceExtensions { - /// - /// Store aggregate changes to the event store - /// /// Event writer or event store - /// Stream name for the aggregate - /// Aggregate instance - /// Optional: function to add extra information to the event before it gets stored - /// Cancellation token - /// Aggregate type - /// Aggregate state type - /// Append event result - /// Gets thrown if the expected stream version mismatches with the given original stream version - [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] - [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] - public static async Task StoreAggregate( - this IEventWriter eventWriter, - StreamName streamName, - TAggregate aggregate, - AmendEvent? amendEvent = null, - CancellationToken cancellationToken = default - ) where TAggregate : Aggregate where TState : State, new() { - Ensure.NotNull(aggregate); - - try { - return await eventWriter.Store(streamName, new(aggregate.OriginalVersion), aggregate.Changes, amendEvent, cancellationToken).NoContext(); - } catch (OptimisticConcurrencyException e) { - Log.UnableToStoreAggregate(streamName, e); - - throw e.InnerException is null ? new OptimisticConcurrencyException(streamName, e) : new(streamName, e.InnerException); + extension(IEventWriter eventWriter) { + /// + /// Store aggregate changes to the event store + /// + /// Stream name for the aggregate + /// Aggregate instance + /// Optional: function to add extra information to the event before it gets stored + /// Cancellation token + /// Aggregate type + /// Aggregate state type + /// Append event result + /// Gets thrown if the expected stream version mismatches with the given original stream version + [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] + [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] + public async Task StoreAggregate( + StreamName streamName, + TAggregate aggregate, + AmendEvent? amendEvent = null, + CancellationToken cancellationToken = default + ) where TAggregate : Aggregate where TState : State, new() { + Ensure.NotNull(aggregate); + + try { + return await eventWriter.Store(streamName, new(aggregate.OriginalVersion), aggregate.Changes, amendEvent, cancellationToken).NoContext(); + } catch (OptimisticConcurrencyException e) { + Log.UnableToStoreAggregate(streamName, e); + + throw e.InnerException is null ? new OptimisticConcurrencyException(streamName, e) : new(streamName, e.InnerException); + } } - } - /// - /// Store aggregate changes to the event store - /// - /// Event writer or event store - /// Aggregate instance - /// Aggregate identity - /// Optional: stream name map - /// Optional: function to add extra information to the event before it gets stored - /// Cancellation token - /// Aggregate type - /// Aggregate state type - /// Aggregate identity type - /// Append event result - /// Gets thrown if the expected stream version mismatches with the given original stream version - [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] - [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] - public static Task StoreAggregate( - this IEventWriter eventWriter, - TAggregate aggregate, - TId id, - StreamNameMap? streamNameMap = null, - AmendEvent? amendEvent = null, - CancellationToken cancellationToken = default - ) where TAggregate : Aggregate where TState : State, new() where TId : Id { - Ensure.NotNull(aggregate); - - if (aggregate.State is State stateWithId && stateWithId.Id != id) { - throw new InvalidOperationException($"Provided aggregate id {id} doesn't match an existing aggregate id {stateWithId.Id}"); + /// + /// Store aggregate changes to the event store + /// + /// Aggregate instance + /// Aggregate identity + /// Optional: stream name map + /// Optional: function to add extra information to the event before it gets stored + /// Cancellation token + /// Aggregate type + /// Aggregate state type + /// Aggregate identity type + /// Append event result + /// Gets thrown if the expected stream version mismatches with the given original stream version + [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] + [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] + public Task StoreAggregate( + TAggregate aggregate, + TId id, + StreamNameMap? streamNameMap = null, + AmendEvent? amendEvent = null, + CancellationToken cancellationToken = default + ) where TAggregate : Aggregate where TState : State, new() where TId : Id { + Ensure.NotNull(aggregate); + + if (aggregate.State is State stateWithId && stateWithId.Id != id) { + throw new InvalidOperationException($"Provided aggregate id {id} doesn't match an existing aggregate id {stateWithId.Id}"); + } + + var streamName = streamNameMap?.GetStreamName(id) ?? StreamNameFactory.For(id); + + return eventWriter.Store(streamName, new(aggregate.OriginalVersion), aggregate.Changes, amendEvent, cancellationToken); } - var streamName = streamNameMap?.GetStreamName(id) ?? StreamNameFactory.For(id); - - return eventWriter.Store(streamName, new(aggregate.OriginalVersion), aggregate.Changes, amendEvent, cancellationToken); - } - - /// - /// Store aggregate changes to the event store - /// - /// Event writer or event store - /// Aggregate instance - /// Optional: stream name map - /// Optional: function to add extra information to the event before it gets stored - /// Cancellation token - /// Aggregate type - /// Aggregate state type - /// Aggregate identity type - /// Append event result - /// Gets thrown if the expected stream version mismatches with the given original stream version - [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] - [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] - public static Task StoreAggregate( - this IEventWriter eventWriter, - TAggregate aggregate, - StreamNameMap? streamNameMap = null, - AmendEvent? amendEvent = null, - CancellationToken cancellationToken = default - ) where TAggregate : Aggregate where TState : State, new() where TId : Id { - Ensure.NotNull(aggregate); - - var streamName = streamNameMap?.GetStreamName(aggregate.State.Id) ?? - StreamNameFactory.For(aggregate.State.Id); - - return eventWriter.Store(streamName, new(aggregate.OriginalVersion), aggregate.Changes, amendEvent, cancellationToken); + /// + /// Store aggregate changes to the event store + /// + /// Aggregate instance + /// Optional: stream name map + /// Optional: function to add extra information to the event before it gets stored + /// Cancellation token + /// Aggregate type + /// Aggregate state type + /// Aggregate identity type + /// Append event result + /// Gets thrown if the expected stream version mismatches with the given original stream version + [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] + [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] + public Task StoreAggregate( + TAggregate aggregate, + StreamNameMap? streamNameMap = null, + AmendEvent? amendEvent = null, + CancellationToken cancellationToken = default + ) where TAggregate : Aggregate where TState : State, new() where TId : Id { + Ensure.NotNull(aggregate); + + var streamName = streamNameMap?.GetStreamName(aggregate.State.Id) ?? + StreamNameFactory.For(aggregate.State.Id); + + return eventWriter.Store(streamName, new(aggregate.OriginalVersion), aggregate.Changes, amendEvent, cancellationToken); + } } - /// - /// Loads aggregate from event store - /// /// Event reader or store - /// Name of the aggregate stream - /// Either fail if the stream is not found, default is false - /// Optional: aggregate factory registry. Default instance will be used if the argument isn't provided. - /// Cancellation token - /// Aggregate type - /// Aggregate state type - /// Aggregate instance - /// If failIfNotFound set to true, this exception is thrown if there's no stream - /// - [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] - [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] - public static async Task LoadAggregate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TAggregate, TState>( - this IEventReader eventReader, - StreamName streamName, - bool failIfNotFound = true, - AggregateFactoryRegistry? factoryRegistry = null, - CancellationToken cancellationToken = default - ) - where TAggregate : Aggregate where TState : State, new() { - var aggregate = (factoryRegistry ?? AggregateFactoryRegistry.Instance).CreateInstance(); - - try { - var events = await eventReader.ReadStream(streamName, StreamReadPosition.Start, failIfNotFound, cancellationToken).NoContext(); - aggregate.Load(events.Select(x => x.Payload)); - } catch (StreamNotFound) when (!failIfNotFound) { - return aggregate; - } catch (Exception e) { - Log.UnableToLoadAggregate(streamName, e); + extension(IEventReader eventReader) { + /// + /// Loads aggregate from event store + /// + /// Name of the aggregate stream + /// Either fail if the stream is not found, default is false + /// Optional: aggregate factory registry. Default instance will be used if the argument isn't provided. + /// Cancellation token + /// Aggregate type + /// Aggregate state type + /// Aggregate instance + /// If failIfNotFound set to true, this exception is thrown if there's no stream + /// + [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] + [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] + public async Task LoadAggregate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TAggregate, TState>( + StreamName streamName, + bool failIfNotFound = true, + AggregateFactoryRegistry? factoryRegistry = null, + CancellationToken cancellationToken = default + ) + where TAggregate : Aggregate where TState : State, new() { + var aggregate = (factoryRegistry ?? AggregateFactoryRegistry.Instance).CreateInstance(); + + try { + var events = await eventReader.ReadStream(streamName, StreamReadPosition.Start, failIfNotFound, cancellationToken).NoContext(); + aggregate.Load(events.Select(x => x.Payload)); + } catch (StreamNotFound) when (!failIfNotFound) { + return aggregate; + } catch (Exception e) { + Log.UnableToLoadAggregate(streamName, e); + + throw e is StreamNotFound ? new AggregateNotFoundException(streamName, e) : e; + } - throw e is StreamNotFound ? new AggregateNotFoundException(streamName, e) : e; + return aggregate; } - return aggregate; - } - - /// - /// Loads aggregate from event store - /// - /// Event reader or store - /// Aggregate identity - /// Optional: stream name map. Default instance is used when argument isn't provided. - /// Either fail if the stream is not found, default is false - /// Optional: aggregate factory registry. Default instance will be used if the argument isn't provided. - /// Cancellation token - /// Aggregate type - /// Aggregate state type - /// Aggregate identity type - /// Aggregate instance - /// If failIfNotFound set to true, this exception is thrown if there's no stream - /// - [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] - [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] - public static async Task LoadAggregate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TAggregate, TState, TId>( - this IEventReader eventReader, - TId aggregateId, - StreamNameMap? streamNameMap = null, - bool failIfNotFound = true, - AggregateFactoryRegistry? factoryRegistry = null, - CancellationToken cancellationToken = default - ) - where TAggregate : Aggregate where TState : State, new() where TId : Id { - var streamName = streamNameMap?.GetStreamName(aggregateId) - ?? StreamNameFactory.For(aggregateId); - var aggregate = await eventReader.LoadAggregate(streamName, failIfNotFound, factoryRegistry, cancellationToken).NoContext(); - - return aggregate.WithId(aggregateId); + /// + /// Loads aggregate from event store + /// + /// Aggregate identity + /// Optional: stream name map. Default instance is used when argument isn't provided. + /// Either fail if the stream is not found, default is false + /// Optional: aggregate factory registry. Default instance will be used if the argument isn't provided. + /// Cancellation token + /// Aggregate type + /// Aggregate state type + /// Aggregate identity type + /// Aggregate instance + /// If failIfNotFound set to true, this exception is thrown if there's no stream + /// + [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] + [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] + public async Task LoadAggregate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TAggregate, TState, TId>( + TId aggregateId, + StreamNameMap? streamNameMap = null, + bool failIfNotFound = true, + AggregateFactoryRegistry? factoryRegistry = null, + CancellationToken cancellationToken = default + ) + where TAggregate : Aggregate where TState : State, new() where TId : Id { + var streamName = streamNameMap?.GetStreamName(aggregateId) + ?? StreamNameFactory.For(aggregateId); + var aggregate = await eventReader.LoadAggregate(streamName, failIfNotFound, factoryRegistry, cancellationToken).NoContext(); + + return aggregate.WithId(aggregateId); + } } } diff --git a/src/Core/src/Eventuous.Persistence/AggregateStore/AggregateStoreExtensions.cs b/src/Core/src/Eventuous.Persistence/AggregateStore/AggregateStoreExtensions.cs index 1789cdb7..296b7858 100644 --- a/src/Core/src/Eventuous.Persistence/AggregateStore/AggregateStoreExtensions.cs +++ b/src/Core/src/Eventuous.Persistence/AggregateStore/AggregateStoreExtensions.cs @@ -4,52 +4,53 @@ namespace Eventuous; public static class AggregateStoreExtensions { - /// - /// Loads an aggregate by its ID, assigns the State.Id property - /// /// Aggregate store instance - /// Stream name map - /// Aggregate id - /// - /// Aggregate type - /// State type - /// Aggregate id type - /// - [Obsolete("Use IEventReader.LoadAggregates instead.")] - [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] - [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] - public static async Task Load<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T, TState, TId>(this IAggregateStore store, StreamNameMap streamNameMap, TId id, CancellationToken cancellationToken) - where T : Aggregate where TId : Id where TState : State, new() { - var aggregate = await store.Load(streamNameMap.GetStreamName(id), cancellationToken).NoContext(); + extension(IAggregateStore store) { + /// + /// Loads an aggregate by its ID, assigns the State.Id property + /// + /// Stream name map + /// Aggregate id + /// + /// Aggregate type + /// State type + /// Aggregate id type + /// + [Obsolete("Use IEventReader.LoadAggregates instead.")] + [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] + [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] + public async Task Load + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T, TState, TId>(StreamNameMap streamNameMap, TId id, CancellationToken cancellationToken) + where T : Aggregate where TId : Id where TState : State, new() { + var aggregate = await store.Load(streamNameMap.GetStreamName(id), cancellationToken).NoContext(); - return aggregate.WithId(id); - } + return aggregate.WithId(id); + } - /// - /// Loads an aggregate by its ID, assigns the State.Id property. - /// If the aggregate stream is not found, returns a new aggregate instance - /// - /// Aggregate store instance - /// Stream name map - /// Aggregate id - /// - /// Aggregate type - /// State type - /// Aggregate id type - /// - [Obsolete("Use IEventReader.LoadAggregates instead.")] - [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] - [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] - public static async Task LoadOrNew<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TAggregate, TState, TId>( - this IAggregateStore store, - StreamNameMap streamNameMap, - TId id, - CancellationToken cancellationToken - ) - where TAggregate : Aggregate where TId : Id where TState : State, new() { - var aggregate = await store.LoadOrNew(streamNameMap.GetStreamName(id), cancellationToken).NoContext(); + /// + /// Loads an aggregate by its ID, assigns the State.Id property. + /// If the aggregate stream is not found, returns a new aggregate instance + /// + /// Stream name map + /// Aggregate id + /// + /// Aggregate type + /// State type + /// Aggregate id type + /// + [Obsolete("Use IEventReader.LoadAggregates instead.")] + [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] + [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] + public async Task LoadOrNew<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TAggregate, TState, TId>( + StreamNameMap streamNameMap, + TId id, + CancellationToken cancellationToken + ) + where TAggregate : Aggregate where TId : Id where TState : State, new() { + var aggregate = await store.LoadOrNew(streamNameMap.GetStreamName(id), cancellationToken).NoContext(); - return aggregate.WithId(id); + return aggregate.WithId(id); + } } internal static TAggregate WithId(this TAggregate aggregate, TId id) diff --git a/src/Core/src/Eventuous.Persistence/StateStore/StateStoreFunctions.cs b/src/Core/src/Eventuous.Persistence/StateStore/StateStoreFunctions.cs index 7e789825..06fe26fc 100644 --- a/src/Core/src/Eventuous.Persistence/StateStore/StateStoreFunctions.cs +++ b/src/Core/src/Eventuous.Persistence/StateStore/StateStoreFunctions.cs @@ -6,63 +6,62 @@ namespace Eventuous; using static Diagnostics.PersistenceEventSource; public static class StateStoreFunctions { - /// - /// Reads the event stream and folds it into a state object. This function will fail if the stream does not exist. - /// /// Event reader or event store - /// Name of the stream to read from - /// When set to false and there's no stream, the function will return an empty instance. - /// Cancellation token - /// State object type - /// Instance of containing events and folded state - /// Thrown if there's no stream and failIfNotFound is true - [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] - [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] - public static async Task> LoadState( - this IEventReader reader, - StreamName streamName, - bool failIfNotFound = true, - CancellationToken cancellationToken = default - ) where TState : State, new() { - try { - var streamEvents = await reader.ReadStream(streamName, StreamReadPosition.Start, failIfNotFound, cancellationToken).NoContext(); - var events = streamEvents.Select(x => x.Payload!).ToArray(); - var expectedVersion = events.Length == 0 ? ExpectedStreamVersion.NoStream : new(streamEvents.Last().Position); + extension(IEventReader reader) { + /// + /// Reads the event stream and folds it into a state object. This function will fail if the stream does not exist. + /// + /// Name of the stream to read from + /// When set to false and there's no stream, the function will return an empty instance. + /// Cancellation token + /// State object type + /// Instance of containing events and folded state + /// Thrown if there's no stream and failIfNotFound is true + [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] + [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] + public async Task> LoadState( + StreamName streamName, + bool failIfNotFound = true, + CancellationToken cancellationToken = default + ) where TState : State, new() { + try { + var streamEvents = await reader.ReadStream(streamName, StreamReadPosition.Start, failIfNotFound, cancellationToken).NoContext(); + var events = streamEvents.Select(x => x.Payload!).ToArray(); + var expectedVersion = events.Length == 0 ? ExpectedStreamVersion.NoStream : new(streamEvents.Last().Position); - return (new(streamName, expectedVersion, events)); - } catch (StreamNotFound) when (!failIfNotFound) { - return new(streamName, ExpectedStreamVersion.NoStream, []); - } catch (Exception e) { - Log.UnableToLoadStream(streamName, e); + return (new(streamName, expectedVersion, events)); + } catch (StreamNotFound) when (!failIfNotFound) { + return new(streamName, ExpectedStreamVersion.NoStream, []); + } catch (Exception e) { + Log.UnableToLoadStream(streamName, e); - throw; + throw; + } } - } - /// - /// Reads the event stream and folds it into a state object. This function will fail if the stream does not exist. - /// - /// Event reader or event store - /// State identity value - /// When set to false and there's no stream, the function will return an empty instance. - /// Cancellation token - /// Mapper between identity and stream name - /// State object type - /// State identity type - /// Instance of containing events and folded state - [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] - [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] - public static async Task> LoadState( - this IEventReader reader, - StreamNameMap streamNameMap, - TId id, - bool failIfNotFound = true, - CancellationToken cancellationToken = default - ) - where TState : State, new() where TId : Id { - var foldedStream = await reader.LoadState(streamNameMap.GetStreamName(id), failIfNotFound, cancellationToken).NoContext(); + /// + /// Reads the event stream and folds it into a state object. This function will fail if the stream does not exist. + /// + /// State identity value + /// When set to false and there's no stream, the function will return an empty instance. + /// Cancellation token + /// Mapper between identity and stream name + /// State object type + /// State identity type + /// Instance of containing events and folded state + [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] + [RequiresUnreferencedCode(AttrConstants.DynamicSerializationMessage)] + public async Task> LoadState( + StreamNameMap streamNameMap, + TId id, + bool failIfNotFound = true, + CancellationToken cancellationToken = default + ) + where TState : State, new() where TId : Id { + var foldedStream = await reader.LoadState(streamNameMap.GetStreamName(id), failIfNotFound, cancellationToken).NoContext(); - return foldedStream with { State = foldedStream.State.WithId(id) }; + return foldedStream with { State = foldedStream.State.WithId(id) }; + } } static TState WithId(this TState state, TId id) where TState : State, new() where TId : Id { diff --git a/src/Core/src/Eventuous.Producers/ProducerExtensions.cs b/src/Core/src/Eventuous.Producers/ProducerExtensions.cs index 00d0743c..9e042dd6 100644 --- a/src/Core/src/Eventuous.Producers/ProducerExtensions.cs +++ b/src/Core/src/Eventuous.Producers/ProducerExtensions.cs @@ -91,5 +91,5 @@ static ProducedMessage[] ConvertOne( AcknowledgeProduce? onAck, ReportFailedProduce? onNack ) - => [new ProducedMessage(message, metadata, additionalHeaders) { OnAck = onAck, OnNack = onNack }]; + => [new(message, metadata, additionalHeaders) { OnAck = onAck, OnNack = onNack }]; } diff --git a/src/Core/src/Eventuous.Producers/RegistrationExtensions.cs b/src/Core/src/Eventuous.Producers/RegistrationExtensions.cs index 98e75537..2ccd2aa5 100644 --- a/src/Core/src/Eventuous.Producers/RegistrationExtensions.cs +++ b/src/Core/src/Eventuous.Producers/RegistrationExtensions.cs @@ -12,59 +12,59 @@ namespace Microsoft.Extensions.DependencyInjection; [PublicAPI] public static class RegistrationExtensions { - [Obsolete("Use AddProducer instead")] - public static void AddEventProducer(this IServiceCollection services, T producer) where T : class, IProducer { - services.AddProducer(producer); - } - - /// - /// Register a producer in the DI container as IProducer using a pre-instantiated instance. - /// /// - /// Producer instance - /// Producer implementation type - public static void AddProducer(this IServiceCollection services, T producer) where T : class, IProducer { - services.TryAddSingleton(producer); - services.TryAddSingleton(sp => sp.GetRequiredService()); + extension(IServiceCollection services) { + [Obsolete("Use AddProducer instead")] + public void AddEventProducer(T producer) where T : class, IProducer { + services.AddProducer(producer); + } - if (producer is IHostedService service) { - services.TryAddSingleton(service); + /// + /// Register a producer in the DI container as IProducer using a pre-instantiated instance. + /// + /// Producer instance + /// Producer implementation type + public void AddProducer(T producer) where T : class, IProducer { + services.TryAddSingleton(producer); + services.TryAddSingleton(sp => sp.GetRequiredService()); + + if (producer is IHostedService service) { + services.TryAddSingleton(service); + } } - } - [Obsolete("Use AddProducer instead")] - public static void AddEventProducer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(this IServiceCollection services, Func getProducer) - where T : class, IProducer { - services.AddProducer(getProducer); - } + [Obsolete("Use AddProducer instead")] + public void AddEventProducer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(Func getProducer) + where T : class, IProducer { + services.AddProducer(getProducer); + } - /// - /// Register a producer in the DI container as IProducer using a factory function. - /// - /// - /// Function to resolve the producer from the service provider - /// Producer implementation type - public static void AddProducer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(this IServiceCollection services, Func getProducer) - where T : class, IProducer { - services.TryAddSingleton(getProducer); - AddCommon(services); - } + /// + /// Register a producer in the DI container as IProducer using a factory function. + /// + /// Function to resolve the producer from the service provider + /// Producer implementation type + public void AddProducer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(Func getProducer) + where T : class, IProducer { + services.TryAddSingleton(getProducer); + AddCommon(services); + } - [Obsolete("Use AddProducer instead")] - public static void AddEventProducer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.Interfaces)] T>(this IServiceCollection services) - where T : class, IProducer { - services.AddProducer(); - } + [Obsolete("Use AddProducer instead")] + public void AddEventProducer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.Interfaces)] T>() + where T : class, IProducer { + services.AddProducer(); + } - /// - /// Register a producer in the DI container as IProducer using the default constructor. - /// - /// - /// Producer implementation type - public static void AddProducer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.Interfaces)] T>(this IServiceCollection services) - where T : class, IProducer { - services.TryAddSingleton(); - AddCommon(services); + /// + /// Register a producer in the DI container as IProducer using the default constructor. + /// + /// Producer implementation type + public void AddProducer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.Interfaces)] T>() + where T : class, IProducer { + services.TryAddSingleton(); + AddCommon(services); + } } static void AddCommon<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(IServiceCollection services) where T : class, IProducer { diff --git a/src/Core/src/Eventuous.Shared/Meta/MetadataExtensions.cs b/src/Core/src/Eventuous.Shared/Meta/MetadataExtensions.cs index 78fc3952..aa5e7021 100644 --- a/src/Core/src/Eventuous.Shared/Meta/MetadataExtensions.cs +++ b/src/Core/src/Eventuous.Shared/Meta/MetadataExtensions.cs @@ -4,33 +4,32 @@ namespace Eventuous; public static class MetadataExtensions { - /// - /// Add correlation id to metadata - /// /// Metadata instance - /// Correlation id value - /// - public static Metadata WithCorrelationId(this Metadata metadata, string? correlationId) => metadata.With(MetaTags.CorrelationId, correlationId); + extension(Metadata metadata) { + /// + /// Add correlation id to metadata + /// + /// Correlation id value + /// + public Metadata WithCorrelationId(string? correlationId) => metadata.With(MetaTags.CorrelationId, correlationId); - /// - /// Add causation id to metadata - /// - /// Metadata instance - /// Causation id value - /// - public static Metadata WithCausationId(this Metadata metadata, string? causationId) => metadata.With(MetaTags.CausationId, causationId); + /// + /// Add causation id to metadata + /// + /// Causation id value + /// + public Metadata WithCausationId(string? causationId) => metadata.With(MetaTags.CausationId, causationId); - /// - /// Get the correlation id from metadata, if available - /// - /// Metadata instance - /// Correlation id or null - public static string? GetCorrelationId(this Metadata metadata) => metadata.GetString(MetaTags.CorrelationId); + /// + /// Get the correlation id from metadata, if available + /// + /// Correlation id or null + public string? GetCorrelationId() => metadata.GetString(MetaTags.CorrelationId); - /// - /// Get the causation id from metadata, if available - /// - /// Metadata instance - /// Causation id or null - public static string? GetCausationId(this Metadata metadata) => metadata.GetString(MetaTags.CausationId); + /// + /// Get the causation id from metadata, if available + /// + /// Causation id or null + public string? GetCausationId() => metadata.GetString(MetaTags.CausationId); + } } diff --git a/src/Core/src/Eventuous.Shared/TypeMap/TypeMapperExtensions.cs b/src/Core/src/Eventuous.Shared/TypeMap/TypeMapperExtensions.cs index b68373cd..b8e04bea 100644 --- a/src/Core/src/Eventuous.Shared/TypeMap/TypeMapperExtensions.cs +++ b/src/Core/src/Eventuous.Shared/TypeMap/TypeMapperExtensions.cs @@ -6,54 +6,55 @@ namespace Eventuous; using static TypeMapEventSource; public static class TypeMapperExtensions { - /// - /// Get the type name for a given type - /// /// Type mapper instance - /// Object type for which the name needs to be retrieved - /// Indicates if exception should be thrown if the type is now registered - /// Type name from the map or "unknown" if the type isn't registered and fail is set to false - /// Thrown if the type isn't registered and fail is set to true - public static string GetTypeNameByType(this ITypeMapper typeMapper, Type type, bool fail = true) { - var typeKnown = typeMapper.TryGetTypeName(type, out var name); - - if (!typeKnown && fail) { - Log.TypeNotMappedToName(type); - - throw new UnregisteredTypeException(type); + extension(ITypeMapper typeMapper) { + /// + /// Get the type name for a given type + /// + /// Object type for which the name needs to be retrieved + /// Indicates if exception should be thrown if the type is now registered + /// Type name from the map or "unknown" if the type isn't registered and fail is set to false + /// Thrown if the type isn't registered and fail is set to true + public string GetTypeNameByType(Type type, bool fail = true) { + var typeKnown = typeMapper.TryGetTypeName(type, out var name); + + if (!typeKnown && fail) { + Log.TypeNotMappedToName(type); + + throw new UnregisteredTypeException(type); + } + + return name ?? ITypeMapper.UnknownType; } - return name ?? ITypeMapper.UnknownType; - } + public string GetTypeName(object o, bool fail = true) => typeMapper.GetTypeNameByType(o.GetType(), fail); - public static string GetTypeName(this ITypeMapper typeMapper, object o, bool fail = true) => typeMapper.GetTypeNameByType(o.GetType(), fail); + public string GetTypeName(bool fail = true) => typeMapper.GetTypeNameByType(typeof(T), fail); - public static string GetTypeName(this ITypeMapper typeMapper, bool fail = true) => typeMapper.GetTypeNameByType(typeof(T), fail); + public bool TryGetTypeName([NotNullWhen(true)] out string? typeName) => typeMapper.TryGetTypeName(typeof(T), out typeName); - public static bool TryGetTypeName(this ITypeMapper typeMapper, [NotNullWhen(true)] out string? typeName) => typeMapper.TryGetTypeName(typeof(T), out typeName); + /// + /// Get the registered type for a given name + /// + /// Type name for which the type needs to be returned + /// Type that matches the given name + /// Thrown if the type isn't registered and fail is set to true + public Type GetType(string typeName) { + var typeKnown = typeMapper.TryGetType(typeName, out var type); - /// - /// Get the registered type for a given name - /// - /// Type mapper instance - /// Type name for which the type needs to be returned - /// Type that matches the given name - /// Thrown if the type isn't registered and fail is set to true - public static Type GetType(this ITypeMapper typeMapper, string typeName) { - var typeKnown = typeMapper.TryGetType(typeName, out var type); + if (!typeKnown) { + Log.TypeNameNotMappedToType(typeName); - if (!typeKnown) { - Log.TypeNameNotMappedToType(typeName); + throw new UnregisteredTypeException(typeName); + } - throw new UnregisteredTypeException(typeName); + return type!; } - return type!; - } - - public static void EnsureTypesRegistered(this ITypeMapper typeMapper, IEnumerable types) { - foreach (var type in types) { - typeMapper.GetTypeNameByType(type); + public void EnsureTypesRegistered(IEnumerable types) { + foreach (var type in types) { + typeMapper.GetTypeNameByType(type); + } } } } diff --git a/src/Core/src/Eventuous.Subscriptions/Channels/ChannelExtensions.cs b/src/Core/src/Eventuous.Subscriptions/Channels/ChannelExtensions.cs index 1cb04fa0..871f7855 100644 --- a/src/Core/src/Eventuous.Subscriptions/Channels/ChannelExtensions.cs +++ b/src/Core/src/Eventuous.Subscriptions/Channels/ChannelExtensions.cs @@ -9,56 +9,56 @@ namespace Eventuous.Subscriptions.Channels; public delegate ValueTask ProcessElement(T element, CancellationToken cancellationToken); static class ChannelExtensions { - public static async Task Read(this Channel channel, ProcessElement process, CancellationToken cancellationToken) { - try { - while (!cancellationToken.IsCancellationRequested) { - var element = await channel.Reader.ReadAsync(cancellationToken).NoContext(); - await process(element, cancellationToken).NoContext(); + extension(Channel channel) { + public async Task Read(ProcessElement process, CancellationToken cancellationToken) { + try { + while (!cancellationToken.IsCancellationRequested) { + var element = await channel.Reader.ReadAsync(cancellationToken).NoContext(); + await process(element, cancellationToken).NoContext(); + } + } catch (OperationCanceledException) { + // it's ok + } catch (ChannelClosedException) { + // ok, we are quitting } - } catch (OperationCanceledException) { - // it's ok - } catch (ChannelClosedException) { - // ok, we are quitting } - } - public static async Task ReadBatches( - this Channel channel, - ProcessElement process, - int maxCount, - TimeSpan maxTime, - CancellationToken cancellationToken - ) { - await foreach (var batch in channel.Reader.ReadAllBatches(maxCount, maxTime, cancellationToken).NoContext(cancellationToken)) { - await process(batch, cancellationToken).NoContext(); + public async Task ReadBatches( + ProcessElement process, + int maxCount, + TimeSpan maxTime, + CancellationToken cancellationToken + ) { + await foreach (var batch in channel.Reader.ReadAllBatches(maxCount, maxTime, cancellationToken).NoContext(cancellationToken)) { + await process(batch, cancellationToken).NoContext(); + } } - } - public static ValueTask Write(this Channel channel, T element, bool throwOnFull, CancellationToken cancellationToken) { - return throwOnFull ? WriteOrThrow() : channel.Writer.WriteAsync(element, cancellationToken); + public ValueTask Write(T element, bool throwOnFull, CancellationToken cancellationToken) { + return throwOnFull ? WriteOrThrow() : channel.Writer.WriteAsync(element, cancellationToken); - ValueTask WriteOrThrow() => !channel.Writer.TryWrite(element) ? throw new ChannelFullException() : default; - } + ValueTask WriteOrThrow() => !channel.Writer.TryWrite(element) ? throw new ChannelFullException() : default; + } - public static async ValueTask Stop( - this Channel channel, - CancellationTokenSource cts, - Task[] readers, - Func? finalize = null - ) { - channel.Writer.TryComplete(); + public async ValueTask Stop( + CancellationTokenSource cts, + Task[] readers, + Func? finalize = null + ) { + channel.Writer.TryComplete(); - var incompleteReaders = readers.Where(r => !r.IsCompleted).ToArray(); + var incompleteReaders = readers.Where(r => !r.IsCompleted).ToArray(); - if (readers.Length > 0) { - cts.CancelAfter(TimeSpan.FromSeconds(10)); - await Task.WhenAll(incompleteReaders).NoContext(); - } + if (readers.Length > 0) { + cts.CancelAfter(TimeSpan.FromSeconds(10)); + await Task.WhenAll(incompleteReaders).NoContext(); + } - if (finalize == null) return; + if (finalize == null) return; - using var ts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - await finalize(ts.Token).NoContext(); + using var ts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + await finalize(ts.Token).NoContext(); + } } static async IAsyncEnumerable ReadAllBatches( diff --git a/src/Core/src/Eventuous.Subscriptions/Context/ContextResultExtensions.cs b/src/Core/src/Eventuous.Subscriptions/Context/ContextResultExtensions.cs index b49dbb5b..a30790e6 100644 --- a/src/Core/src/Eventuous.Subscriptions/Context/ContextResultExtensions.cs +++ b/src/Core/src/Eventuous.Subscriptions/Context/ContextResultExtensions.cs @@ -10,91 +10,86 @@ namespace Eventuous.Subscriptions.Context; using Logging; public static class ContextResultExtensions { - /// - /// Allows acknowledging the message by a specific handler, identified by a string - /// /// Consume context - /// Handler type identifier - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Ack(this IBaseConsumeContext context, string handlerType) { - context.HandlingResults.Add(EventHandlingResult.Succeeded(handlerType)); - context.LogContext.MessageHandled(handlerType, context); - } + extension(IBaseConsumeContext context) { + /// + /// Allows acknowledging the message by a specific handler, identified by a string + /// + /// Handler type identifier + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Ack(string handlerType) { + context.HandlingResults.Add(EventHandlingResult.Succeeded(handlerType)); + context.LogContext.MessageHandled(handlerType, context); + } - /// - /// Allows conveying the message handling failure that occurred in a specific handler - /// - /// Message context - /// Handler type identifier - /// Optional: handler exception - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Nack(this IBaseConsumeContext context, string handlerType, Exception? exception) { - context.HandlingResults.Add(EventHandlingResult.Failed(handlerType, exception)); + /// + /// Allows conveying the message handling failure that occurred in a specific handler + /// + /// Handler type identifier + /// Optional: handler exception + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Nack(string handlerType, Exception? exception) { + context.HandlingResults.Add(EventHandlingResult.Failed(handlerType, exception)); - context.LogContext.MessageHandlingFailed(handlerType, context, exception); + context.LogContext.MessageHandlingFailed(handlerType, context, exception); - if (Activity.Current != null && Activity.Current.Status != ActivityStatusCode.Error) { - Activity.Current.SetActivityStatus( - ActivityStatus.Error(exception, $"Error handling {context.MessageType}") - ); + if (Activity.Current != null && Activity.Current.Status != ActivityStatusCode.Error) { + Activity.Current.SetActivityStatus( + ActivityStatus.Error(exception, $"Error handling {context.MessageType}") + ); + } } - } - /// - /// Allows conveying the fact that the message was ignored by the handler - /// - /// Consume context - /// Handler type identifier - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Ignore(this IBaseConsumeContext context, string handlerType) { - context.HandlingResults.Add(EventHandlingResult.Ignored(handlerType)); - context.LogContext.MessageIgnored(handlerType, context); - } + /// + /// Allows conveying the fact that the message was ignored by the handler + /// + /// Handler type identifier + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Ignore(string handlerType) { + context.HandlingResults.Add(EventHandlingResult.Ignored(handlerType)); + context.LogContext.MessageIgnored(handlerType, context); + } - /// - /// Allows acknowledging the message by a specific handler, identified by a string - /// - /// Consume context - /// Handler type - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Ack(this IBaseConsumeContext context) => context.Ack(typeof(T).Name); + /// + /// Allows acknowledging the message by a specific handler, identified by a string + /// + /// Handler type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Ack() => context.Ack(typeof(T).Name); - /// - /// Allows conveying the fact that the message was ignored by the handler - /// - /// Consume context - /// Handler type - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Ignore(this IBaseConsumeContext context) => context.Ignore(typeof(T).Name); + /// + /// Allows conveying the fact that the message was ignored by the handler + /// + /// Handler type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Ignore() => context.Ignore(typeof(T).Name); - /// - /// Allows conveying the message handling failure that occurred in a specific handler - /// - /// Consume context - /// Optional: handler exception - /// Handler type - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Nack(this IBaseConsumeContext context, Exception? exception) => context.Nack(typeof(T).Name, exception); + /// + /// Allows conveying the message handling failure that occurred in a specific handler + /// + /// Optional: handler exception + /// Handler type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Nack(Exception? exception) => context.Nack(typeof(T).Name, exception); - /// - /// Returns true if the message was ignored by all handlers - /// - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool WasIgnored(this IBaseConsumeContext context) { - var status = context.HandlingResults.GetIgnoreStatus(); - var handleStatus = context.HandlingResults.GetFailureStatus(); + /// + /// Returns true if the message was ignored by all handlers + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool WasIgnored() { + var status = context.HandlingResults.GetIgnoreStatus(); + var handleStatus = context.HandlingResults.GetFailureStatus(); - return (status & EventHandlingStatus.Ignored) == EventHandlingStatus.Ignored && handleStatus == 0; - } + return (status & EventHandlingStatus.Ignored) == EventHandlingStatus.Ignored && handleStatus == 0; + } - /// - /// Returns true if any of the handlers reported a failure - /// - /// Consume context - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasFailed(this IBaseConsumeContext context) - => context.HandlingResults.GetFailureStatus() == EventHandlingStatus.Failure; + /// + /// Returns true if any of the handlers reported a failure + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasFailed() + => context.HandlingResults.GetFailureStatus() == EventHandlingStatus.Failure; + } } diff --git a/src/Core/src/Eventuous.Subscriptions/EventSubscriptionWithCheckpoint.cs b/src/Core/src/Eventuous.Subscriptions/EventSubscriptionWithCheckpoint.cs index 4181135b..6ab5d210 100644 --- a/src/Core/src/Eventuous.Subscriptions/EventSubscriptionWithCheckpoint.cs +++ b/src/Core/src/Eventuous.Subscriptions/EventSubscriptionWithCheckpoint.cs @@ -97,7 +97,7 @@ protected async Task GetCheckpoint(CancellationToken cancellationTok LoggerFactory ); - if (IsRunning && LastProcessed != null) { return new Checkpoint(Options.SubscriptionId, LastProcessed?.Position); } + if (IsRunning && LastProcessed != null) { return new(Options.SubscriptionId, LastProcessed?.Position); } Logger.Current = Log; diff --git a/src/Core/src/Eventuous.Subscriptions/Logging/CheckpointLogging.cs b/src/Core/src/Eventuous.Subscriptions/Logging/CheckpointLogging.cs index 0cec3698..0dfdaa19 100644 --- a/src/Core/src/Eventuous.Subscriptions/Logging/CheckpointLogging.cs +++ b/src/Core/src/Eventuous.Subscriptions/Logging/CheckpointLogging.cs @@ -10,27 +10,31 @@ namespace Eventuous.Subscriptions.Logging; using Checkpoints; public static class CheckpointLogging { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void PositionReceived(this LogContext log, CommitPosition checkpoint) - => log.TraceLog?.Log("Received checkpoint: {Position}", checkpoint); + extension(LogContext log) { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PositionReceived(CommitPosition checkpoint) + => log.TraceLog?.Log("Received checkpoint: {Position}", checkpoint); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CommittingPosition(this LogContext log, CommitPosition position) - => log.DebugLog?.Log("Committing position {Position}", position); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CommittingPosition(CommitPosition position) + => log.DebugLog?.Log("Committing position {Position}", position); - public static void UnableToCommitPosition(this LogContext log, CommitPosition position, Exception exception) - => log.ErrorLog?.Log(exception, "Unable to commit position {Position}", position); + public void UnableToCommitPosition(CommitPosition position, Exception exception) + => log.ErrorLog?.Log(exception, "Unable to commit position {Position}", position); + } - public static void CheckpointLoaded(this LogContext? log, ICheckpointStore store, Checkpoint checkpoint) - => log?.InfoLog?.Log("Loaded checkpoint {CheckpointId} from {Store}: {Position}", checkpoint.Id, store.GetType().Name, checkpoint); + extension(LogContext? log) { + public void CheckpointLoaded(ICheckpointStore store, Checkpoint checkpoint) + => log?.InfoLog?.Log("Loaded checkpoint {CheckpointId} from {Store}: {Position}", checkpoint.Id, store.GetType().Name, checkpoint); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CheckpointStored(this LogContext? log, ICheckpointStore store, Checkpoint checkpoint, bool force) { - if (log == null) return; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CheckpointStored(ICheckpointStore store, Checkpoint checkpoint, bool force) { + if (log == null) return; - const string message = "Stored checkpoint {CheckpointId} in {Store}: {Position}"; + const string message = "Stored checkpoint {CheckpointId} in {Store}: {Position}"; - if (force) log.InfoLog?.Log(message, checkpoint.Id, store.GetType().Name, checkpoint); - else log.TraceLog?.Log(message, checkpoint.Id, store.GetType().Name, checkpoint); + if (force) log.InfoLog?.Log(message, checkpoint.Id, store.GetType().Name, checkpoint); + else log.TraceLog?.Log(message, checkpoint.Id, store.GetType().Name, checkpoint); + } } } diff --git a/src/Core/src/Eventuous.Subscriptions/Logging/SubscriptionLogging.cs b/src/Core/src/Eventuous.Subscriptions/Logging/SubscriptionLogging.cs index 741dac4b..a46f141f 100644 --- a/src/Core/src/Eventuous.Subscriptions/Logging/SubscriptionLogging.cs +++ b/src/Core/src/Eventuous.Subscriptions/Logging/SubscriptionLogging.cs @@ -9,90 +9,88 @@ namespace Eventuous.Subscriptions.Logging; using Context; public static class LoggingExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MessageReceived(this LogContext log, IMessageConsumeContext context) - => log.TraceLog?.Log( - "Received {MessageType} from {Stream}:{Position} seq {Sequence}", - context.MessageType, - context.Stream, - context.GlobalPosition, - context.Sequence - ); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MessageHandled(this LogContext log, string handlerType, IBaseConsumeContext context) - => log.TraceLog?.Log( - "{Handler} handled {MessageType} {Stream}:{Position} seq {Sequence}", - handlerType, - context.MessageType, - context.Stream, - context.GlobalPosition, - context.Sequence - ); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MessageIgnored(this LogContext log, string handlerType, IBaseConsumeContext context) - => log.TraceLog?.Log( - "{Handler} ignored {MessageType} {Stream}:{Position} seq {Sequence}", - handlerType, - context.MessageType, - context.Stream, - context.GlobalPosition, - context.Sequence - ); - - public static void MessageIgnoredWhenStopping(this LogContext log, Exception e) => - log.DebugLog?.Log("Message ignored because subscription is stopping: {Message}", e.Message); + extension(LogContext log) { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MessageReceived(IMessageConsumeContext context) + => log.TraceLog?.Log( + "Received {MessageType} from {Stream}:{Position} seq {Sequence}", + context.MessageType, + context.Stream, + context.GlobalPosition, + context.Sequence + ); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MessageHandled(string handlerType, IBaseConsumeContext context) + => log.TraceLog?.Log( + "{Handler} handled {MessageType} {Stream}:{Position} seq {Sequence}", + handlerType, + context.MessageType, + context.Stream, + context.GlobalPosition, + context.Sequence + ); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MessageIgnored(string handlerType, IBaseConsumeContext context) + => log.TraceLog?.Log( + "{Handler} ignored {MessageType} {Stream}:{Position} seq {Sequence}", + handlerType, + context.MessageType, + context.Stream, + context.GlobalPosition, + context.Sequence + ); + + public void MessageIgnoredWhenStopping(Exception e) => + log.DebugLog?.Log("Message ignored because subscription is stopping: {Message}", e.Message); + + public void MessageHandlerNotFound(string handler, string messageType) + => log.WarnLog?.Log("No handler found in {Handler} for message type {MessageType}", handler, messageType); + + public void MessageHandlingFailed(string handlerType, IBaseConsumeContext context, Exception? exception) + => log.ErrorLog?.Log(exception, "Message handling failed at {HandlerType} for message {MessageId}", handlerType, context.MessageId); + + public void PayloadDeserializationFailed(string stream, ulong position, string messageType, Exception exception) + => log.ErrorLog?.Log(exception, "Failed to deserialize event {MessageType} at {Stream}:{Position}", messageType, stream, position); + + public void MetadataDeserializationFailed(string stream, ulong position, Exception exception) + => log.ErrorLog?.Log(exception, "Failed to deserialize metadata at {Stream}:{Position}", stream, position); + + public void MessagePayloadInconclusive(string messageType, string stream, DeserializationError error) + => log.DebugLog?.Log("Message of type {MessageType} from {Stream} ignored as it didn't deserialize: {Error}", messageType, stream, error); + + public void ThrowOnErrorIncompatible() + => log.WarnLog?.Log("Failure handler is set, but ThrowOnError is disabled, so the failure handler will never be called"); + + public void FailedToHandleMessageWithRetry(string handlerType, string messageType, int retryCount, Exception exception) + => log.ErrorLog?.Log( + exception, + "Failed to handle message {MessageType} with {HandlerType} after {RetryCount} retries", + messageType, + handlerType, + retryCount + ); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MessageAcked(string messageType, ulong position) + => log.TraceLog?.Log("Message {Type} acknowledged at {Position}", messageType, position); + + public void MessageNacked(string messageType, ulong position, Exception exception) + => log.WarnLog?.Log(exception, "Message {Type} not acknowledged at {Position}", messageType, position); + + public void SubscriptionStarted() => log.InfoLog?.Log("Started"); + public void SubscriptionStopped() => log.InfoLog?.Log("Stopped"); + + public void SubscriptionDropped(DropReason reason, Exception? exception) + => log.WarnLog?.Log(exception, "Dropped: {Reason}", reason); + + public void SubscriptionWillResubscribe(TimeSpan delay) => log.WarnLog?.Log($"Will resubscribe after {delay}"); + public void SubscriptionResubscribing() => log.WarnLog?.Log("Resubscribing"); + public void SubscriptionResubscribed() => log.InfoLog?.Log("Resubscribed"); + public void SubscriptionResubscribeFailed(Exception e) => log.ErrorLog?.Log(e, "Failed to resubscribe"); + } public static void MessageTypeNotFound(this ILogger? log) => log?.LogWarning("Message type {MessageType} not registered in the type map", typeof(T).Name); - - public static void MessageHandlerNotFound(this LogContext log, string handler, string messageType) - => log.WarnLog?.Log("No handler found in {Handler} for message type {MessageType}", handler, messageType); - - public static void MessageHandlingFailed(this LogContext log, string handlerType, IBaseConsumeContext context, Exception? exception) - => log.ErrorLog?.Log(exception, "Message handling failed at {HandlerType} for message {MessageId}", handlerType, context.MessageId); - - public static void PayloadDeserializationFailed(this LogContext log, string stream, ulong position, string messageType, Exception exception) - => log.ErrorLog?.Log(exception, "Failed to deserialize event {MessageType} at {Stream}:{Position}", messageType, stream, position); - - public static void MetadataDeserializationFailed(this LogContext log, string stream, ulong position, Exception exception) - => log.ErrorLog?.Log(exception, "Failed to deserialize metadata at {Stream}:{Position}", stream, position); - - public static void MessagePayloadInconclusive(this LogContext log, string messageType, string stream, DeserializationError error) - => log.DebugLog?.Log("Message of type {MessageType} from {Stream} ignored as it didn't deserialize: {Error}", messageType, stream, error); - - public static void ThrowOnErrorIncompatible(this LogContext log) - => log.WarnLog?.Log("Failure handler is set, but ThrowOnError is disabled, so the failure handler will never be called"); - - public static void FailedToHandleMessageWithRetry(this LogContext log, string handlerType, string messageType, int retryCount, Exception exception) - => log.ErrorLog?.Log( - exception, - "Failed to handle message {MessageType} with {HandlerType} after {RetryCount} retries", - messageType, - handlerType, - retryCount - ); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MessageAcked(this LogContext log, string messageType, ulong position) - => log.TraceLog?.Log("Message {Type} acknowledged at {Position}", messageType, position); - - public static void MessageNacked(this LogContext log, string messageType, ulong position, Exception exception) - => log.WarnLog?.Log(exception, "Message {Type} not acknowledged at {Position}", messageType, position); - - public static void SubscriptionStarted(this LogContext log) => log.InfoLog?.Log("Started"); - - public static void SubscriptionStopped(this LogContext log) => log.InfoLog?.Log("Stopped"); - - public static void SubscriptionDropped(this LogContext log, DropReason reason, Exception? exception) - => log.WarnLog?.Log(exception, "Dropped: {Reason}", reason); - - public static void SubscriptionWillResubscribe(this LogContext log, TimeSpan delay) => log.WarnLog?.Log($"Will resubscribe after {delay}"); - - public static void SubscriptionResubscribing(this LogContext log) => log.WarnLog?.Log("Resubscribing"); - - public static void SubscriptionResubscribed(this LogContext log) => log.InfoLog?.Log("Resubscribed"); - - public static void SubscriptionResubscribeFailed(this LogContext log, Exception e) => log.ErrorLog?.Log(e, "Failed to resubscribe"); } diff --git a/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilderExtensions.cs b/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilderExtensions.cs index e42ca23d..eb4fd15f 100644 --- a/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilderExtensions.cs +++ b/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilderExtensions.cs @@ -13,108 +13,104 @@ namespace Eventuous.Subscriptions.Registrations; using System.Diagnostics.CodeAnalysis; public static class SubscriptionBuilderExtensions { - /// - /// Adds partitioning to the subscription. Keep in mind that not all subscriptions can support partitioned consume. - /// /// Subscription builder - /// Number of partitions - /// Function to get the partition key from the context - /// - [PublicAPI] - public static SubscriptionBuilder WithPartitioning(this SubscriptionBuilder builder, int partitionsCount, Partitioner.GetPartitionKey getPartitionKey) - => builder.AddConsumeFilterFirst(new PartitioningFilter(partitionsCount, getPartitionKey)); - - /// - /// Adds partitioning to the subscription using the stream name as partition key. - /// Keep in mind that not all subscriptions can support partitioned consume. - /// - /// Subscription builder - /// Number of partitions - /// - [PublicAPI] - public static SubscriptionBuilder WithPartitioningByStream(this SubscriptionBuilder builder, int partitionsCount) - => builder.WithPartitioning(partitionsCount, ctx => ctx.Stream); - - /// - /// Use non-default checkpoint store for the specific subscription - /// - /// Subscription builder - /// Checkpoint store type - /// - public static SubscriptionBuilder UseCheckpointStore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this SubscriptionBuilder builder) - where T : class, ICheckpointStore { - builder.Services.TryAddKeyedSingleton(builder.SubscriptionId); - - if (EventuousDiagnostics.Enabled) { - builder.Services.TryAddKeyedSingleton( - builder.SubscriptionId, - (sp, key) => new MeasuredCheckpointStore(sp.GetRequiredKeyedService(key)) - ); - } - else { - builder.Services.TryAddKeyedSingleton(builder.SubscriptionId); + extension(SubscriptionBuilder builder) { + /// + /// Adds partitioning to the subscription. Keep in mind that not all subscriptions can support partitioned consume. + /// + /// Number of partitions + /// Function to get the partition key from the context + /// + [PublicAPI] + public SubscriptionBuilder WithPartitioning(int partitionsCount, Partitioner.GetPartitionKey getPartitionKey) + => builder.AddConsumeFilterFirst(new PartitioningFilter(partitionsCount, getPartitionKey)); + + /// + /// Adds partitioning to the subscription using the stream name as partition key. + /// Keep in mind that not all subscriptions can support partitioned consume. + /// + /// Number of partitions + /// + [PublicAPI] + public SubscriptionBuilder WithPartitioningByStream(int partitionsCount) + => builder.WithPartitioning(partitionsCount, ctx => ctx.Stream); + + /// + /// Use non-default checkpoint store for the specific subscription + /// + /// Checkpoint store type + /// + public SubscriptionBuilder UseCheckpointStore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() + where T : class, ICheckpointStore { + builder.Services.TryAddKeyedSingleton(builder.SubscriptionId); + + if (EventuousDiagnostics.Enabled) { + builder.Services.TryAddKeyedSingleton( + builder.SubscriptionId, + (sp, key) => new MeasuredCheckpointStore(sp.GetRequiredKeyedService(key)) + ); + } + else { + builder.Services.TryAddKeyedSingleton(builder.SubscriptionId); + } + + return builder; } - return builder; - } - - /// - /// Use non-default checkpoint store for the specific subscription - /// - /// Subscription builder - /// Function to resolve the checkpoint store service from service provider - /// Checkpoint store type - /// - public static SubscriptionBuilder UseCheckpointStore(this SubscriptionBuilder builder, Func factory) - where T : class, ICheckpointStore { - if (EventuousDiagnostics.Enabled) { - builder.Services.TryAddKeyedSingleton( - builder.SubscriptionId, - (sp, _) => new MeasuredCheckpointStore(factory(sp)) - ); - } - else { - builder.Services.TryAddKeyedSingleton(builder.SubscriptionId, (sp, _) => factory(sp)); + /// + /// Use non-default checkpoint store for the specific subscription + /// + /// Function to resolve the checkpoint store service from service provider + /// Checkpoint store type + /// + public SubscriptionBuilder UseCheckpointStore(Func factory) + where T : class, ICheckpointStore { + if (EventuousDiagnostics.Enabled) { + builder.Services.TryAddKeyedSingleton( + builder.SubscriptionId, + (sp, _) => new MeasuredCheckpointStore(factory(sp)) + ); + } + else { + builder.Services.TryAddKeyedSingleton(builder.SubscriptionId, (sp, _) => factory(sp)); + } + + return builder; } - return builder; - } + /// + /// Use non-default serializer for the specific subscription + /// + /// Function to create the serializer instance + /// Serializer type + /// + public SubscriptionBuilder UseSerializer(Func factory) where T : class, IEventSerializer { + builder.Services.TryAddKeyedSingleton(builder.SubscriptionId, (sp, _) => factory(sp)); - /// - /// Use non-default serializer for the specific subscription - /// - /// Subscription builder - /// Function to create the serializer instance - /// Serializer type - /// - public static SubscriptionBuilder UseSerializer(this SubscriptionBuilder builder, Func factory) where T : class, IEventSerializer { - builder.Services.TryAddKeyedSingleton(builder.SubscriptionId, (sp, _) => factory(sp)); - - return builder; - } + return builder; + } - /// - /// Use non-default serializer for the specific subscription - /// - /// Subscription builder - /// Serializer type - /// - public static SubscriptionBuilder UseSerializer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this SubscriptionBuilder builder) where T : class, IEventSerializer { - builder.Services.TryAddKeyedSingleton(builder.SubscriptionId); + /// + /// Use non-default serializer for the specific subscription + /// + /// Serializer type + /// + public SubscriptionBuilder UseSerializer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() where T : class, IEventSerializer { + builder.Services.TryAddKeyedSingleton(builder.SubscriptionId); - return builder; - } + return builder; + } - /// - /// Use non-default type mapper for the specific subscription - /// - /// Subscription builder - /// Custom type mapper instance - /// Type mapper type - /// - public static SubscriptionBuilder UseTypeMapper(this SubscriptionBuilder builder, T typeMapper) where T : class, ITypeMapper { - builder.Services.TryAddKeyedSingleton(builder.SubscriptionId, typeMapper); + /// + /// Use non-default type mapper for the specific subscription + /// + /// Custom type mapper instance + /// Type mapper type + /// + public SubscriptionBuilder UseTypeMapper(T typeMapper) where T : class, ITypeMapper { + builder.Services.TryAddKeyedSingleton(builder.SubscriptionId, typeMapper); - return builder; + return builder; + } } } diff --git a/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionRegistrationExtensions.cs b/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionRegistrationExtensions.cs index 707be3e4..fdf087a9 100644 --- a/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionRegistrationExtensions.cs +++ b/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionRegistrationExtensions.cs @@ -65,18 +65,20 @@ string[] tags return builder.AddCheck(checkName, failureStatus, tags); } - public static IServiceCollection AddCheckpointStore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this IServiceCollection services) - where T : class, ICheckpointStore { - services.AddSingleton(); + extension(IServiceCollection services) { + public IServiceCollection AddCheckpointStore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() + where T : class, ICheckpointStore { + services.AddSingleton(); - return AddCheckpointStoreInternal(services); - } + return AddCheckpointStoreInternal(services); + } - public static IServiceCollection AddCheckpointStore(this IServiceCollection services, Func getStore) - where T : class, ICheckpointStore { - services.AddSingleton(getStore); + public IServiceCollection AddCheckpointStore(Func getStore) + where T : class, ICheckpointStore { + services.AddSingleton(getStore); - return AddCheckpointStoreInternal(services); + return AddCheckpointStoreInternal(services); + } } static void TryAddSubscriptionHealthCheck(IServiceCollection services) { diff --git a/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/Helpers.cs b/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/Helpers.cs index 891d60e7..71c0694a 100644 --- a/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/Helpers.cs +++ b/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/Helpers.cs @@ -16,35 +16,20 @@ public static IEnumerable CreateEvents(this StoreFixtureBase fi static BookingImported ToEvent(ImportBooking cmd) => new(cmd.RoomId, cmd.Price, cmd.CheckIn, cmd.CheckOut); - public static Task AppendEvents( - this StoreFixtureBase fixture, - StreamName stream, - object[] evt, - ExpectedStreamVersion version - ) { - var streamEvents = evt.Select(x => new NewStreamEvent(Guid.NewGuid(), x, new())); - - return fixture.EventStore.AppendEvents(stream, version, streamEvents.ToArray(), default); - } + extension(StoreFixtureBase fixture) { + public Task AppendEvents(StreamName stream, object[] evt, ExpectedStreamVersion version) { + var streamEvents = evt.Select(x => new NewStreamEvent(Guid.NewGuid(), x, new())); - public static Task AppendEvent( - this StoreFixtureBase fixture, - StreamName stream, - object evt, - ExpectedStreamVersion version, - Metadata? metadata = null - ) { - var streamEvent = new NewStreamEvent(Guid.NewGuid(), evt, metadata ?? new Metadata()); + return fixture.EventStore.AppendEvents(stream, version, streamEvents.ToArray(), default); + } - return fixture.EventStore.AppendEvents(stream, version, [streamEvent], default); - } + public Task AppendEvent(StreamName stream, object evt, ExpectedStreamVersion version, Metadata? metadata = null) { + var streamEvent = new NewStreamEvent(Guid.NewGuid(), evt, metadata ?? new Metadata()); + + return fixture.EventStore.AppendEvents(stream, version, [streamEvent], default); + } - public static Task StoreChanges( - this StoreFixtureBase fixture, - StreamName stream, - object evt, - ExpectedStreamVersion version - ) { - return fixture.EventStore.Store(stream, version, [evt]); + public Task StoreChanges(StreamName stream, object evt, ExpectedStreamVersion version) + => fixture.EventStore.Store(stream, version, [evt]); } } diff --git a/src/Core/test/Eventuous.Tests.Shared.Analyzers/Analyzer_Ev001_Tests.cs b/src/Core/test/Eventuous.Tests.Shared.Analyzers/Analyzer_Ev001_Tests.cs index 310bca60..5eb62ed4 100644 --- a/src/Core/test/Eventuous.Tests.Shared.Analyzers/Analyzer_Ev001_Tests.cs +++ b/src/Core/test/Eventuous.Tests.Shared.Analyzers/Analyzer_Ev001_Tests.cs @@ -53,7 +53,7 @@ static CSharpCompilation CreateCompilation(string source) { MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location), MetadataReference.CreateFromFile(typeof(State<>).Assembly.Location), MetadataReference.CreateFromFile(typeof(Aggregate<>).Assembly.Location), - MetadataReference.CreateFromFile(typeof(EventTypeAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(EventTypeAttribute).Assembly.Location) }; // Some frameworks need additional facades depending on runtime; try to add them if present diff --git a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionExtensions.cs b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionExtensions.cs index 16edd47a..6c1f6048 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionExtensions.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionExtensions.cs @@ -4,16 +4,18 @@ namespace Eventuous.Tests.Subscriptions.Base; public static class SubscriptionExtensions { - public static ValueTask SubscribeWithLog(this IMessageSubscription subscription, ILogger log, CancellationToken cancellationToken = default) - => subscription.Subscribe( - id => log.LogInformation("{Subscription} subscribed", id), - (id, reason, ex) => log.LogWarning(ex, "{Subscription} dropped {Reason}", id, reason), - cancellationToken - ); + extension(IMessageSubscription subscription) { + public ValueTask SubscribeWithLog(ILogger log, CancellationToken cancellationToken = default) + => subscription.Subscribe( + id => log.LogInformation("{Subscription} subscribed", id), + (id, reason, ex) => log.LogWarning(ex, "{Subscription} dropped {Reason}", id, reason), + cancellationToken + ); - public static ValueTask UnsubscribeWithLog(this IMessageSubscription subscription, ILogger log, CancellationToken cancellationToken = default) - => subscription.Unsubscribe( - id => log.LogInformation("{Subscription} unsubscribed", id), - cancellationToken - ); + public ValueTask UnsubscribeWithLog(ILogger log, CancellationToken cancellationToken = default) + => subscription.Unsubscribe( + id => log.LogInformation("{Subscription} unsubscribed", id), + cancellationToken + ); + } } \ No newline at end of file diff --git a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/TestEventHandler.cs b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/TestEventHandler.cs index 916e0648..8ef13f17 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/TestEventHandler.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/TestEventHandler.cs @@ -14,7 +14,7 @@ namespace Eventuous.Tests.Subscriptions.Base; public record TestEvent(string Data, int Number) { public const string TypeName = "test-event"; - static readonly Faker Faker = new Faker().CustomInstantiator(f => new TestEvent(f.Lorem.Sentence(), f.Random.Int())); + static readonly Faker Faker = new Faker().CustomInstantiator(f => new(f.Lorem.Sentence(), f.Random.Int())); public static TestEvent Create() => Faker.Generate(); diff --git a/src/Core/test/Eventuous.Tests.Subscriptions/SequenceTests.cs b/src/Core/test/Eventuous.Tests.Subscriptions/SequenceTests.cs index 8d17b766..cc43beba 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions/SequenceTests.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions/SequenceTests.cs @@ -27,7 +27,7 @@ public void ShouldReturnFirstBefore(CommitPositionSequence sequence, CommitPosit public void ShouldWorkForOne() { var timestamp = DateTime.Now; var sequence = new CommitPositionSequence { new(0, 1, timestamp) }; - sequence.FirstBeforeGap().ShouldBe(new CommitPosition(0, 1, timestamp)); + sequence.FirstBeforeGap().ShouldBe(new(0, 1, timestamp)); } [Test] @@ -58,7 +58,7 @@ public void ShouldWorkForNormalCase() { } var first = sequence.FirstBeforeGap(); - first.ShouldBe(new CommitPosition(9, 9, timestamp)); + first.ShouldBe(new(9, 9, timestamp)); } public static IEnumerable> TestData() { diff --git a/src/Diagnostics/src/Eventuous.Diagnostics.OpenTelemetry/MeterProviderBuilderExtensions.cs b/src/Diagnostics/src/Eventuous.Diagnostics.OpenTelemetry/MeterProviderBuilderExtensions.cs index 74d61e26..8115d131 100644 --- a/src/Diagnostics/src/Eventuous.Diagnostics.OpenTelemetry/MeterProviderBuilderExtensions.cs +++ b/src/Diagnostics/src/Eventuous.Diagnostics.OpenTelemetry/MeterProviderBuilderExtensions.cs @@ -10,45 +10,46 @@ namespace Eventuous.Diagnostics.OpenTelemetry; [PublicAPI] public static class MeterProviderBuilderExtensions { - /// - /// Adds subscription metrics instrumentation - /// /// - /// - /// - public static MeterProviderBuilder AddEventuousSubscriptions(this MeterProviderBuilder builder, TagList? customTags = null) - => Ensure.NotNull(builder).AddMeter(SubscriptionMetrics.MeterName).AddMetrics(customTags); + extension(MeterProviderBuilder builder) { + /// + /// Adds subscription metrics instrumentation + /// + /// + /// + public MeterProviderBuilder AddEventuousSubscriptions(TagList? customTags = null) + => Ensure.NotNull(builder).AddMeter(SubscriptionMetrics.MeterName).AddMetrics(customTags); - /// - /// Adds metrics instrumentation for core components such as application service and event store - /// - /// - /// - /// - public static MeterProviderBuilder AddEventuous(this MeterProviderBuilder builder, TagList? customTags = null) - => Ensure.NotNull(builder) - .AddMeter(CommandServiceMetrics.MeterName) - .AddMetrics(customTags) - .AddMeter(PersistenceMetrics.MeterName) - .AddMetrics(customTags); + /// + /// Adds metrics instrumentation for core components such as application service and event store + /// + /// + /// + public MeterProviderBuilder AddEventuous(TagList? customTags = null) + => Ensure.NotNull(builder) + .AddMeter(CommandServiceMetrics.MeterName) + .AddMetrics(customTags) + .AddMeter(PersistenceMetrics.MeterName) + .AddMetrics(customTags); - static MeterProviderBuilder AddMetrics<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this MeterProviderBuilder builder, TagList? customTags = null) - where T : class, IWithCustomTags { - builder.ConfigureServices(services => services.AddSingleton()); + MeterProviderBuilder AddMetrics<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(TagList? customTags = null) + where T : class, IWithCustomTags { + builder.ConfigureServices(services => services.AddSingleton()); - return builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder - ? deferredMeterProviderBuilder.Configure( - (sp, b) => { - b.AddInstrumentation( - () => { - var instrument = sp.GetRequiredService(); - if (customTags != null) instrument.SetCustomTags(customTags.Value); + return builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder + ? deferredMeterProviderBuilder.Configure( + (sp, b) => { + b.AddInstrumentation( + () => { + var instrument = sp.GetRequiredService(); + if (customTags != null) instrument.SetCustomTags(customTags.Value); - return instrument; - } - ); - } - ) - : builder; + return instrument; + } + ); + } + ) + : builder; + } } } diff --git a/src/Diagnostics/src/Eventuous.Diagnostics.OpenTelemetry/TracerProviderBuilderExtensions.cs b/src/Diagnostics/src/Eventuous.Diagnostics.OpenTelemetry/TracerProviderBuilderExtensions.cs index 46028d00..4afe7f82 100644 --- a/src/Diagnostics/src/Eventuous.Diagnostics.OpenTelemetry/TracerProviderBuilderExtensions.cs +++ b/src/Diagnostics/src/Eventuous.Diagnostics.OpenTelemetry/TracerProviderBuilderExtensions.cs @@ -25,7 +25,7 @@ class PollingSampler : Sampler { public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) { return samplingParameters.ParentContext is { TraceFlags: ActivityTraceFlags.None } && samplingParameters is { Kind: ActivityKind.Client, Name: "eventuous" } ? new SamplingResult(SamplingDecision.Drop) - : new SamplingResult(SamplingDecision.RecordAndSample); + : new(SamplingDecision.RecordAndSample); } } } diff --git a/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/MetricsTests.cs b/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/MetricsTests.cs index bd966a78..000af069 100644 --- a/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/MetricsTests.cs +++ b/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/MetricsTests.cs @@ -63,13 +63,15 @@ public void Teardown() { } static class TagExtensions { - public static async Task CheckTag(this MetricValue metric, string tag, string expectedValue) { - await Assert.That(metric.GetTag(tag)).IsEqualTo(expectedValue); - } + extension(MetricValue metric) { + public async Task CheckTag(string tag, string expectedValue) { + await Assert.That(metric.GetTag(tag)).IsEqualTo(expectedValue); + } - static object GetTag(this MetricValue metric, string key) { - var index = metric.Keys.Select((x, i) => (x, i)).First(x => x.x == key).i; + object GetTag(string key) { + var index = metric.Keys.Select((x, i) => (x, i)).First(x => x.x == key).i; - return metric.Values[index]; + return metric.Values[index]; + } } } diff --git a/src/Experimental/src/ElasticPlayground/ConfigureElastic.cs b/src/Experimental/src/ElasticPlayground/ConfigureElastic.cs index 2441fb2e..499db9f2 100644 --- a/src/Experimental/src/ElasticPlayground/ConfigureElastic.cs +++ b/src/Experimental/src/ElasticPlayground/ConfigureElastic.cs @@ -8,25 +8,25 @@ public static class ConfigureElastic { public static async Task ConfigureIndex(this ElasticClient client) { var config = new IndexConfig { IndexName = "eventuous", - Lifecycle = new LifecycleConfig { + Lifecycle = new() { PolicyName = "eventuous", Tiers = [ - new TierDefinition { + new() { Tier = "hot", MinAge = "1d", Priority = 100, - Rollover = new Rollover { + Rollover = new() { MaxAge = "1d", MaxSize = "100mb" } }, - new TierDefinition { + new() { Tier = "warm", MinAge = "1d", Priority = 50, - ForceMerge = new ForceMerge { MaxNumSegments = 1 } + ForceMerge = new() { MaxNumSegments = 1 } }, - new TierDefinition { + new() { Tier = "cold", MinAge = "1d", Priority = 0, @@ -34,9 +34,7 @@ public static async Task ConfigureIndex(this ElasticClient client) { } ] }, - Template = new DataStreamTemplateConfig { - TemplateName = "eventuous" - } + Template = new() { TemplateName = "eventuous" } }; await client.CreateIndexIfNecessary(config); diff --git a/src/Experimental/src/ElasticPlayground/ElasticPlayground.csproj b/src/Experimental/src/ElasticPlayground/ElasticPlayground.csproj index 865d23d5..3bf2878d 100644 --- a/src/Experimental/src/ElasticPlayground/ElasticPlayground.csproj +++ b/src/Experimental/src/ElasticPlayground/ElasticPlayground.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0 Exe false true diff --git a/src/Experimental/src/ElasticPlayground/MiscExtensions.cs b/src/Experimental/src/ElasticPlayground/MiscExtensions.cs index 309e0154..8f5837c7 100644 --- a/src/Experimental/src/ElasticPlayground/MiscExtensions.cs +++ b/src/Experimental/src/ElasticPlayground/MiscExtensions.cs @@ -2,16 +2,15 @@ // Licensed under the Apache License, Version 2.0. using Eventuous.Sut.App; -using Eventuous.Sut.Domain; namespace ElasticPlayground; public static class MiscExtensions { public static Commands.RecordPayment ToRecordPayment(this Commands.BookRoom command, string paymentId, float divider = 1) => new( - new BookingId(command.BookingId), + new(command.BookingId), paymentId, - new Money(command.Price / divider), + new(command.Price / divider), DateTimeOffset.Now ); } diff --git a/src/Experimental/src/Eventuous.Spyglass/Accessor.cs b/src/Experimental/src/Eventuous.Spyglass/Accessor.cs index 83115b11..c9906bdd 100644 --- a/src/Experimental/src/Eventuous.Spyglass/Accessor.cs +++ b/src/Experimental/src/Eventuous.Spyglass/Accessor.cs @@ -6,10 +6,12 @@ namespace Eventuous.Spyglass; static class Accessor { - public static object? GetPrivateMember(this object instance, string name) => GetMember(instance.GetType(), instance, name); + extension(object instance) { + public object? GetPrivateMember(string name) => GetMember(instance.GetType(), instance, name); - public static TMember? GetPrivateMember(this object instance, string name) where TMember : class - => GetMember(instance.GetType(), instance, name); + public TMember? GetPrivateMember(string name) where TMember : class + => GetMember(instance.GetType(), instance, name); + } static TMember? GetMember(Type instanceType, object instance, string name) where TMember : class => GetMember(instanceType, instance, name) as TMember; diff --git a/src/Experimental/src/Eventuous.Spyglass/InsidePeek.cs b/src/Experimental/src/Eventuous.Spyglass/InsidePeek.cs index 516bf8c8..e71f2172 100644 --- a/src/Experimental/src/Eventuous.Spyglass/InsidePeek.cs +++ b/src/Experimental/src/Eventuous.Spyglass/InsidePeek.cs @@ -48,7 +48,7 @@ void Scan(Assembly assembly) { var methods = (type as dynamic).DeclaredMethods as MethodInfo[]; - AggregateInfos.Add(new AggregateInfo(type, stateType, methods!, () => CreateInstance(reg, type))); + AggregateInfos.Add(new(type, stateType, methods!, () => CreateInstance(reg, type))); } return; @@ -70,7 +70,7 @@ static dynamic CreateInstance(Dictionary> reg, Type aggregat public async Task Load(string streamName, int version) { var typeName = streamName[..streamName.IndexOf('-')]; var agg = AggregateInfos.First(x => x.AggregateType == typeName); - var events = await _eventStore.ReadStream(new StreamName(streamName), StreamReadPosition.Start, true, CancellationToken.None); + var events = await _eventStore.ReadStream(new(streamName), StreamReadPosition.Start, true, CancellationToken.None); var aggregate = agg.GetAggregate(); var selectedEvents = version == -1 ? events : events.Take(version + 1); aggregate.Load(selectedEvents.Select(x => x.Payload)); diff --git a/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/Diagnostics.cs b/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/Diagnostics.cs index 47c8f22f..315f7046 100644 --- a/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/Diagnostics.cs +++ b/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/Diagnostics.cs @@ -8,8 +8,13 @@ namespace Eventuous.Extensions.AspNetCore.Generators; internal static class Diagnostics { public const string DiagnosticCategory = "Eventuous.Extensions.AspNetCore"; + public const string DiagnosticId = "EVTA001"; + public const string RouteDiagnosticId = "EVTA002"; + public const string DuplicateRouteId = "EVTA003"; + public const string ParentStateMismatchId = "EVTA004"; + internal static readonly DiagnosticDescriptor StateMatchRule = new( - "EVTA001", + DiagnosticId, "HttpCommand state type mismatches MapCommands state", "Command {0} is mapped to state {1} but the route builder is for state {2}", DiagnosticCategory, @@ -19,7 +24,7 @@ internal static class Diagnostics { ); internal static readonly DiagnosticDescriptor RouteRule = new( - "EVTA002", + RouteDiagnosticId, "HttpCommand route override mismatches attribute route", "Command {0} attribute route '{1}' does not match route override '{2}'", DiagnosticCategory, @@ -29,7 +34,7 @@ internal static class Diagnostics { ); internal static readonly DiagnosticDescriptor DuplicateRouteDiagnostic = new( - id: "EVTA003", + id: DuplicateRouteId, title: "Duplicate route detected", messageFormat: "Duplicate route detected: {0}", category: DiagnosticCategory, @@ -38,7 +43,7 @@ internal static class Diagnostics { ); internal static readonly DiagnosticDescriptor ParentStateMatchDiagnostic = new( - id: "EVTA004", + id: ParentStateMismatchId, title: "State type mismatch", messageFormat: "Command '{0}' state type '{1}' doesn't match with parent state type '{2}'", category: DiagnosticCategory, diff --git a/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/Eventuous.Extensions.AspNetCore.Generators.csproj b/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/Eventuous.Extensions.AspNetCore.Generators.csproj index ffb96459..5cf5ffb0 100644 --- a/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/Eventuous.Extensions.AspNetCore.Generators.csproj +++ b/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/Eventuous.Extensions.AspNetCore.Generators.csproj @@ -10,7 +10,10 @@ - - + + + + + diff --git a/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/HttpCommandStateMismatchAnalyzer.cs b/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/HttpCommandStateMismatchAnalyzer.cs index cae9c98c..2a9280d2 100644 --- a/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/HttpCommandStateMismatchAnalyzer.cs +++ b/src/Extensions/gen/Eventuous.Extensions.AspNetCore.Generators/HttpCommandStateMismatchAnalyzer.cs @@ -14,17 +14,6 @@ namespace Eventuous.Extensions.AspNetCore.Generators; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class HttpCommandStateMismatchAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "EVTA001"; - public const string RouteDiagnosticId = "EVTA002"; - - static readonly LocalizableString RouteMessageFormat = - "Command {0} attribute route '{1}' does not match route override '{2}'"; - - static readonly LocalizableString Description = - "When using MapCommands().MapCommand(...), the TContract decorated with HttpCommandAttribute must have T matching the TState of the route builder."; - - static readonly LocalizableString RouteDescription = - "When an HttpCommandAttribute specifies a Route and MapCommand is called with an explicit route override, the values should match."; const string NamespaceName = "Eventuous.Extensions.AspNetCore.Http"; const string BuilderTypeName = "CommandServiceRouteBuilder"; @@ -33,7 +22,6 @@ public class HttpCommandStateMismatchAnalyzer : DiagnosticAnalyzer { const string StateTypeParamName = "StateType"; const string RouteParamName = "Route"; - public override ImmutableArray SupportedDiagnostics => [StateMatchRule, RouteRule]; public override void Initialize(AnalysisContext context) { @@ -46,9 +34,8 @@ static void AnalyzeInvocation(SyntaxNodeAnalysisContext context) { var invocation = (InvocationExpressionSyntax)context.Node; // Get the invoked method symbol - var symbol = context.SemanticModel.GetSymbolInfo(invocation, context.CancellationToken).Symbol as IMethodSymbol; - if (symbol == null) return; + if (context.SemanticModel.GetSymbolInfo(invocation, context.CancellationToken).Symbol is not IMethodSymbol symbol) return; // We care about MapCommand invocations only if (symbol.Name != "MapCommand") return; @@ -83,13 +70,11 @@ static void AnalyzeInvocation(SyntaxNodeAnalysisContext context) { } } } - else if (symbol is { ContainingType: not null } containing && containing.ContainingType is { } || symbol.ContainingType is { }) { + else if (symbol is { ContainingType: not null } || symbol.ContainingType is not null) { // Fallback to previous logic using method symbol's containing type (in case receiver type retrieval fails) var containingType = symbol.ContainingType; - if (containingType != null - && containingType.Name == BuilderTypeName - && containingType.Arity == 1 + if (containingType is { Name: BuilderTypeName, Arity: 1 } && containingType.ContainingNamespace.ToDisplayString() == NamespaceName && invocation.Expression is MemberAccessExpressionSyntax { Name: GenericNameSyntax { TypeArgumentList.Arguments.Count: 2 } gname }) { var tState = containingType.TypeArguments.FirstOrDefault(); diff --git a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/CommandMappingRegistry.cs b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/CommandMappingRegistry.cs index 96111025..df54900a 100644 --- a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/CommandMappingRegistry.cs +++ b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/CommandMappingRegistry.cs @@ -17,7 +17,7 @@ public static class CommandMappingRegistry { public readonly record struct Bound(Type CommandType, ConfigureEndpoint Map); static readonly ConcurrentDictionary> PerState = new(); - static readonly List All = []; + static readonly List All = []; // TODO: Figure out what to do with it // ReSharper disable once CollectionNeverQueried.Local diff --git a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/HttpCommandMapping.cs b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/HttpCommandMapping.cs index 7edef518..493ecad0 100644 --- a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/HttpCommandMapping.cs +++ b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/HttpCommandMapping.cs @@ -17,110 +17,103 @@ namespace Microsoft.AspNetCore.Routing; public delegate TCommand EnrichCommandFromHttpContext(TCommand command, HttpContext httpContext); public static partial class RouteBuilderExtensions { - /// - /// Map command to HTTP POST endpoint. - /// The HTTP command type should be annotated with attribute. - /// /// Endpoint route builder instance - /// A function to populate command props from HttpContext - /// Command type - /// State type on which the command will operate - /// - public static RouteHandlerBuilder MapCommand( - this IEndpointRouteBuilder builder, - EnrichCommandFromHttpContext? enrichCommand = null - ) - where TState : State, new() - where TCommand : class { - var attr = typeof(TCommand).GetAttribute(); - - return builder.MapCommand(attr?.Route, enrichCommand, attr?.PolicyName); - } + extension(IEndpointRouteBuilder builder) { + /// + /// Map command to HTTP POST endpoint. + /// The HTTP command type should be annotated with attribute. + /// + /// A function to populate command props from HttpContext + /// Command type + /// State type on which the command will operate + /// + public RouteHandlerBuilder MapCommand( + EnrichCommandFromHttpContext? enrichCommand = null + ) + where TState : State, new() + where TCommand : class { + var attr = typeof(TCommand).GetAttribute(); - /// - /// Map command to HTTP POST endpoint. - /// - /// Endpoint route builder instance - /// HTTP API route - /// A function to populate command props from HttpContext - /// Authorization policy - /// Command type - /// State type on which the command will operate - /// - public static RouteHandlerBuilder MapCommand( - this IEndpointRouteBuilder builder, - [StringSyntax("Route")] string? route, - EnrichCommandFromHttpContext? enrichCommand = null, - string? policyName = null - ) - where TState : State, new() - where TCommand : class - => MapInternal( - builder, - route, - enrichCommand != null ? (command, context) => enrichCommand(command, context) : null, - policyName - ); - - /// - /// Creates an instance of for a given aggregate type, so you - /// can explicitly map commands to HTTP endpoints. - /// - /// Endpoint route builder instance - /// State type - /// - public static CommandServiceRouteBuilder MapCommands(this IEndpointRouteBuilder builder) - where TState : State, new() => new(builder); - - /// - /// Maps all commands annotated by to HTTP endpoints to be handled - /// by where TState is the state type provided. - /// Only use it if your application only handles commands for one state type. - /// - /// Endpoint route builder instance - /// Exclude command types - /// State type - /// - /// - public static IEndpointRouteBuilder MapDiscoveredCommands(this IEndpointRouteBuilder builder, params Type[] exclude) - where TState : State { - foreach (var (commandType, map) in CommandMappingRegistry.GetForState(typeof(TState))) { - if (exclude.Contains(commandType)) continue; - map(builder); + return builder.MapCommand(attr?.Route, enrichCommand, attr?.PolicyName); } - // Bind commands that didn't have explicit state at generation time - // AZ: Ignore for now, as it's not clear how to handle this case - // foreach (var unbound in CommandMappingRegistry.GetWithoutState()) { - // builder.LocalMap(typeof(TState), unbound.CommandType, unbound.Route, unbound.Policy); - // } - return builder; - } - /// - /// Maps commands that are annotated either with and/or - /// in given assemblies. Will use assemblies of the current - /// application domain if no assembly is specified explicitly. - /// - /// Endpoint router builder instance - /// Exclude command types - /// - /// - [PublicAPI] - public static IEndpointRouteBuilder MapDiscoveredCommands(this IEndpointRouteBuilder builder, params Type[] exclude) { - // Use generated registry, no reflection/assembly scanning - foreach (var (commandType, map) in CommandMappingRegistry.GetAll()) { - if (exclude.Contains(commandType)) continue; - map(builder); + /// + /// Map command to HTTP POST endpoint. + /// + /// HTTP API route + /// A function to populate command props from HttpContext + /// Authorization policy + /// Command type + /// State type on which the command will operate + /// + public RouteHandlerBuilder MapCommand( + [StringSyntax("Route")] string? route, + EnrichCommandFromHttpContext? enrichCommand = null, + string? policyName = null + ) + where TState : State, new() + where TCommand : class + => MapInternal( + builder, + route, + enrichCommand != null ? (command, context) => enrichCommand(command, context) : null, + policyName + ); + + /// + /// Creates an instance of for a given aggregate type, so you + /// can explicitly map commands to HTTP endpoints. + /// + /// State type + /// + public CommandServiceRouteBuilder MapCommands() + where TState : State, new() => new(builder); + + /// + /// Maps all commands annotated by to HTTP endpoints to be handled + /// by where TState is the state type provided. + /// Only use it if your application only handles commands for one state type. + /// + /// Exclude command types + /// State type + /// + /// + public IEndpointRouteBuilder MapDiscoveredCommands(params Type[] exclude) + where TState : State { + foreach (var (commandType, map) in CommandMappingRegistry.GetForState(typeof(TState))) { + if (exclude.Contains(commandType)) continue; + + map(builder); + } + + // Bind commands that didn't have explicit state at generation time + // AZ: Ignore for now, as it's not clear how to handle this case + // foreach (var unbound in CommandMappingRegistry.GetWithoutState()) { + // builder.LocalMap(typeof(TState), unbound.CommandType, unbound.Route, unbound.Policy); + // } + return builder; } - return builder; - } - // static void LocalMap(this IEndpointRouteBuilder builder, Type stateType, Type type, string? route, string? policyName) { - // var genericMethod = MapMethod.MakeGenericMethod(stateType, type, type); - // genericMethod.Invoke(null, [builder, route, null, policyName]); - // } + /// + /// Maps commands that are annotated either with and/or + /// in given assemblies. Will use assemblies of the current + /// application domain if no assembly is specified explicitly. + /// + /// Exclude command types + /// + /// + [PublicAPI] + public IEndpointRouteBuilder MapDiscoveredCommands(params Type[] exclude) { + // Use generated registry, no reflection/assembly scanning + foreach (var (commandType, map) in CommandMappingRegistry.GetAll()) { + if (exclude.Contains(commandType)) continue; + + map(builder); + } - // static readonly MethodInfo MapMethod = typeof(RouteBuilderExtensions).GetMethod(nameof(MapInternal), BindingFlags.Static | BindingFlags.NonPublic)!; + return builder; + } + } static RouteHandlerBuilder MapInternal( IEndpointRouteBuilder builder, diff --git a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/HttpCommandMappingExt.cs b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/HttpCommandMappingExt.cs index 7c1514ff..b05d6598 100644 --- a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/HttpCommandMappingExt.cs +++ b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/HttpCommandMappingExt.cs @@ -13,41 +13,40 @@ namespace Microsoft.AspNetCore.Routing; public delegate TCommand ConvertAndEnrichCommand(TContract command, HttpContext httpContext); public static partial class RouteBuilderExtensions { - /// - /// Map command to HTTP POST endpoint. - /// The HTTP command type should be annotated with attribute. - /// /// Endpoint route builder instance - /// Function to convert HTTP command to domain command - /// HTTP command type - /// Domain command type - /// State type - /// - public static RouteHandlerBuilder MapCommand( - this IEndpointRouteBuilder builder, - ConvertAndEnrichCommand convert - ) where TState : State, new() where TCommand : class where TContract : class { - var attr = typeof(TContract).GetAttribute(); + extension(IEndpointRouteBuilder builder) { + /// + /// Map command to HTTP POST endpoint. + /// The HTTP command type should be annotated with attribute. + /// + /// Function to convert HTTP command to domain command + /// HTTP command type + /// Domain command type + /// State type + /// + public RouteHandlerBuilder MapCommand( + ConvertAndEnrichCommand convert + ) where TState : State, new() where TCommand : class where TContract : class { + var attr = typeof(TContract).GetAttribute(); - return MapInternal(builder, attr?.Route, convert, attr?.PolicyName); - } + return MapInternal(builder, attr?.Route, convert, attr?.PolicyName); + } - /// - /// Map command to HTTP POST endpoint - /// - /// Endpoint route builder instance - /// API route for the POST endpoint - /// Function to convert HTTP command to domain command - /// Optional authorization policy name - /// HTTP command type - /// Domain command type - /// State type - /// - public static RouteHandlerBuilder MapCommand( - this IEndpointRouteBuilder builder, - [StringSyntax("Route")] string? route, - ConvertAndEnrichCommand convert, - string? policyName = null - ) where TState : State, new() where TCommand : class where TContract : class - => MapInternal(builder, route, convert, policyName); + /// + /// Map command to HTTP POST endpoint + /// + /// API route for the POST endpoint + /// Function to convert HTTP command to domain command + /// Optional authorization policy name + /// HTTP command type + /// Domain command type + /// State type + /// + public RouteHandlerBuilder MapCommand( + [StringSyntax("Route")] string? route, + ConvertAndEnrichCommand convert, + string? policyName = null + ) where TState : State, new() where TCommand : class where TContract : class + => MapInternal(builder, route, convert, policyName); + } } diff --git a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/ResultExtensions.cs b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/ResultExtensions.cs index b5376471..034acc15 100644 --- a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/ResultExtensions.cs +++ b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/ResultExtensions.cs @@ -9,46 +9,48 @@ namespace Eventuous.Extensions.AspNetCore; static class ResultExtensions { - public static IResult AsResult(this Result result) where TState : State, new() { - return result.Match( - Results.Ok, - error => error.Exception switch { - OptimisticConcurrencyException => AsProblem(error, Status409Conflict), - AggregateNotFoundException => AsProblem(error, Status404NotFound), - DomainException => AsValidationProblem(error, Status400BadRequest), - _ => AsProblem(error, Status500InternalServerError) - } - ); - - static IResult AsProblem(Result.Error error, int statusCode) - => Results.Problem(PopulateDetails(new ProblemDetails(), error, statusCode)); - - static IResult AsValidationProblem(Result.Error error, int statusCode) - => Results.Problem(PopulateDetails(new ValidationProblemDetails(error.AsErrors()), error, statusCode)); - } - - public static ActionResult AsActionResult(this Result result) where TState : State, new() { - return result.Match( - ok => new OkObjectResult(ok), - error => - error.Exception switch { + extension(Result result) where TState : State, new() { + public IResult AsResult() { + return result.Match( + Results.Ok, + error => error.Exception switch { OptimisticConcurrencyException => AsProblem(error, Status409Conflict), AggregateNotFoundException => AsProblem(error, Status404NotFound), DomainException => AsValidationProblem(error, Status400BadRequest), _ => AsProblem(error, Status500InternalServerError) } - ); + ); + + static IResult AsProblem(Result.Error error, int statusCode) + => Results.Problem(PopulateDetails(new ProblemDetails(), error, statusCode)); + + static IResult AsValidationProblem(Result.Error error, int statusCode) + => Results.Problem(PopulateDetails(new ValidationProblemDetails(error.AsErrors()), error, statusCode)); + } + + public ActionResult AsActionResult() { + return result.Match( + ok => new OkObjectResult(ok), + error => + error.Exception switch { + OptimisticConcurrencyException => AsProblem(error, Status409Conflict), + AggregateNotFoundException => AsProblem(error, Status404NotFound), + DomainException => AsValidationProblem(error, Status400BadRequest), + _ => AsProblem(error, Status500InternalServerError) + } + ); - static ActionResult AsProblem(Result.Error error, int statusCode) => CreateResult(error, new ProblemDetails(), statusCode); + static ActionResult AsProblem(Result.Error error, int statusCode) => CreateResult(error, new ProblemDetails(), statusCode); - static ActionResult AsValidationProblem(Result.Error error, int statusCode) - => CreateResult(error, new ValidationProblemDetails(error.AsErrors()), statusCode); + static ActionResult AsValidationProblem(Result.Error error, int statusCode) + => CreateResult(error, new ValidationProblemDetails(error.AsErrors()), statusCode); - static ActionResult CreateResult(Result.Error error, T details, int statusCode) where T : ProblemDetails - => new ObjectResult(PopulateDetails(details, error, statusCode)) { - StatusCode = statusCode, - ContentTypes = [ContentTypes.ProblemDetails] - }; + static ActionResult CreateResult(Result.Error error, T details, int statusCode) where T : ProblemDetails + => new ObjectResult(PopulateDetails(details, error, statusCode)) { + StatusCode = statusCode, + ContentTypes = [ContentTypes.ProblemDetails] + }; + } } static T PopulateDetails(T details, Result.Error error, int statusCode) where T : ProblemDetails where TState : State, new() { diff --git a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/RouteHandlerBuilderExt.cs b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/RouteHandlerBuilderExt.cs index b1e954b3..294d08c7 100644 --- a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/RouteHandlerBuilderExt.cs +++ b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/RouteHandlerBuilderExt.cs @@ -7,19 +7,43 @@ namespace Eventuous.Extensions.AspNetCore.Http; static class RouteHandlerBuilderExt { - public static RouteHandlerBuilder ProducesValidationProblemDetails(this RouteHandlerBuilder builder, int statusCode) - => builder.Produces(statusCode, ContentTypes.ProblemDetails); - - public static RouteHandlerBuilder ProducesProblemDetails(this RouteHandlerBuilder builder, int statusCode) - => builder.Produces(statusCode, ContentTypes.ProblemDetails); - - static RouteHandlerBuilder ProducesOk(this RouteHandlerBuilder builder, Type resultType) - => builder.Produces(StatusCodes.Status200OK, resultType, ContentTypes.Json); - - public static RouteHandlerBuilder ProducesOk(this RouteHandlerBuilder builder) where TState : class, new() - => builder.ProducesOk(typeof(Result.Ok)); - - static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder, Type commandType) => builder.Accepts(commandType, false, ContentTypes.Json); - - public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder) => builder.Accepts(typeof(T)); + extension(RouteHandlerBuilder builder) { + /// + /// Configures the route to produce a payload + /// with the specified HTTP status code and the application/problem+json content type. + /// + /// The HTTP status code to declare for validation problem responses. + /// The same instance for chaining. + public RouteHandlerBuilder ProducesValidationProblemDetails(int statusCode) + => builder.Produces(statusCode, ContentTypes.ProblemDetails); + + /// + /// Configures the route to produce a payload + /// with the specified HTTP status code and the application/problem+json content type. + /// + /// The HTTP status code to declare for problem responses. + /// The same instance for chaining. + public RouteHandlerBuilder ProducesProblemDetails(int statusCode) + => builder.Produces(statusCode, ContentTypes.ProblemDetails); + + RouteHandlerBuilder ProducesOk(Type resultType) + => builder.Produces(StatusCodes.Status200OK, resultType, ContentTypes.Json); + + /// + /// Declares a successful 200 OK response for the route with a JSON body + /// containing for the specified state type. + /// + /// The state type wrapped by the successful result. + /// The same instance for chaining. + public RouteHandlerBuilder ProducesOk() where TState : class, new() + => builder.ProducesOk(typeof(Result.Ok)); + + RouteHandlerBuilder Accepts(Type commandType) => builder.Accepts(commandType, false, ContentTypes.Json); + /// + /// Declares that the route accepts a JSON request body of the specified type. + /// + /// The request/command type accepted by the route. + /// The same instance for chaining. + public RouteHandlerBuilder Accepts() => builder.Accepts(typeof(T)); + } } diff --git a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateFactory.cs b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateFactory.cs index e478cdf4..939a034f 100644 --- a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateFactory.cs +++ b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateFactory.cs @@ -8,39 +8,40 @@ namespace Microsoft.Extensions.DependencyInjection; [PublicAPI] public static class AggregateFactoryContainerExtensions { - /// - /// Add an aggregate factory to the container, allowing to resolve aggregate dependencies. - /// Do not use this if your aggregate has no dependencies and has a parameterless constructor. - /// Must be followed by "UseAggregateFactory" for IHost or IApplicationBuilder. - /// /// - /// Aggregate factory function, which can get dependencies from the container. - /// Aggregate type - /// Aggregate state type - /// - public static IServiceCollection AddAggregate(this IServiceCollection services, Func createInstance) - where T : Aggregate where TState : State, new() { - services.TryAddSingleton(); - services.AddSingleton(new ResolveAggregateFactory(typeof(T), createInstance)); + extension(IServiceCollection services) { + /// + /// Add an aggregate factory to the container, allowing to resolve aggregate dependencies. + /// Do not use this if your aggregate has no dependencies and has a parameterless constructor. + /// Must be followed by "UseAggregateFactory" for IHost or IApplicationBuilder. + /// + /// Aggregate factory function, which can get dependencies from the container. + /// Aggregate type + /// Aggregate state type + /// + public IServiceCollection AddAggregate(Func createInstance) + where T : Aggregate where TState : State, new() { + services.TryAddSingleton(); + services.AddSingleton(new ResolveAggregateFactory(typeof(T), createInstance)); - return services; - } + return services; + } - /// - /// Add a default aggregate factory to the container, allowing to resolve aggregate dependencies. - /// Do not use this if your aggregate has no dependencies and has a parameterless constructor. - /// Must be followed by builder.UseAggregateFactory() in Startup.Configure. - /// - /// - /// Aggregate type - /// Aggregate state type - /// - public static IServiceCollection AddAggregate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, TState>(this IServiceCollection services) where T : Aggregate where TState : State, new() { - services.TryAddSingleton(); - services.AddTransient(); - // ReSharper disable once ConvertToLocalFunction - Func createInstance = sp => sp.GetRequiredService(); + /// + /// Add a default aggregate factory to the container, allowing to resolve aggregate dependencies. + /// Do not use this if your aggregate has no dependencies and has a parameterless constructor. + /// Must be followed by builder.UseAggregateFactory() in Startup.Configure. + /// + /// Aggregate type + /// Aggregate state type + /// + public IServiceCollection AddAggregate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, TState>() where T : Aggregate where TState : State, new() { + services.TryAddSingleton(); + services.AddTransient(); + // ReSharper disable once ConvertToLocalFunction + Func createInstance = sp => sp.GetRequiredService(); - return services.AddSingleton(new ResolveAggregateFactory(typeof(T), createInstance)); + return services.AddSingleton(new ResolveAggregateFactory(typeof(T), createInstance)); + } } } diff --git a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateStore.cs b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateStore.cs index c2be8714..d289c7da 100644 --- a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateStore.cs +++ b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateStore.cs @@ -8,64 +8,65 @@ namespace Microsoft.Extensions.DependencyInjection; public static class AggregateStoreRegistrationExtensions { - /// - /// Registers the aggregate store using the supplied type - /// /// - /// Implementation of - /// - [Obsolete("Use AddEventStore instead.")] - public static IServiceCollection AddAggregateStore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this IServiceCollection services) - where T : class, IEventStore { - services.TryAddSingleton(); - services.TryAddSingleton(); - - if (EventuousDiagnostics.Enabled) { services.TryAddSingleton(sp => TracedEventStore.Trace(sp.GetRequiredService())); } - else { services.TryAddSingleton(sp => sp.GetRequiredService()); } + extension(IServiceCollection services) { + /// + /// Registers the aggregate store using the supplied type + /// + /// Implementation of + /// + [Obsolete("Use AddEventStore instead.")] + public IServiceCollection AddAggregateStore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() + where T : class, IEventStore { + services.TryAddSingleton(); + services.TryAddSingleton(); - services.AddSingleton(); + if (EventuousDiagnostics.Enabled) { services.TryAddSingleton(sp => TracedEventStore.Trace(sp.GetRequiredService())); } + else { services.TryAddSingleton(sp => sp.GetRequiredService()); } - return services; - } - - /// - /// Registers the aggregate store using the supplied type - /// - /// - /// Function to create an instance of - /// Implementation of - /// - [Obsolete("Use AddEventStore instead.")] - public static IServiceCollection AddAggregateStore(this IServiceCollection services, Func getService) where T : class, IEventStore { - services.TryAddSingleton(); + services.AddSingleton(); - if (EventuousDiagnostics.Enabled) { - services.TryAddSingleton(getService); - services.TryAddSingleton(sp => TracedEventStore.Trace(sp.GetRequiredService())); + return services; } - else { services.TryAddSingleton(getService); } - services.AddSingleton(); + /// + /// Registers the aggregate store using the supplied type + /// + /// Function to create an instance of + /// Implementation of + /// + [Obsolete("Use AddEventStore instead.")] + public IServiceCollection AddAggregateStore(Func getService) where T : class, IEventStore { + services.TryAddSingleton(); - return services; - } + if (EventuousDiagnostics.Enabled) { + services.TryAddSingleton(getService); + services.TryAddSingleton(sp => TracedEventStore.Trace(sp.GetRequiredService())); + } + else { services.TryAddSingleton(getService); } - [Obsolete("Use AddEventStore and TieredEventReader instead.")] - public static IServiceCollection AddAggregateStore - <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TArchive>(this IServiceCollection services) - where T : class, IEventStore where TArchive : class, IEventReader { - services.TryAddSingleton(); + services.AddSingleton(); - if (EventuousDiagnostics.Enabled) { - services.TryAddSingleton(); - services.TryAddSingleton(sp => TracedEventStore.Trace(sp.GetRequiredService())); + return services; } - else { services.TryAddSingleton(); } - services.TryAddSingleton(); - services.AddSingleton>(); + [Obsolete("Use AddEventStore and TieredEventReader instead.")] + public IServiceCollection AddAggregateStore + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TArchive>() + where T : class, IEventStore where TArchive : class, IEventReader { + services.TryAddSingleton(); + + if (EventuousDiagnostics.Enabled) { + services.TryAddSingleton(); + services.TryAddSingleton(sp => TracedEventStore.Trace(sp.GetRequiredService())); + } + else { services.TryAddSingleton(); } - return services; + services.TryAddSingleton(); + services.AddSingleton>(); + + return services; + } } } diff --git a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/Services.cs b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/Services.cs index 1db9419f..09021ae0 100644 --- a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/Services.cs +++ b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/Services.cs @@ -8,47 +8,48 @@ namespace Microsoft.Extensions.DependencyInjection; public static partial class ServiceCollectionExtensions { - /// - /// Registers the application service in the container - /// /// - /// - /// - /// - public static IServiceCollection AddCommandService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, TState>(this IServiceCollection services) - where T : class, ICommandService - where TState : State, new() { - services.AddSingleton(); - - if (EventuousDiagnostics.Enabled) { - services.AddSingleton(sp => TracedCommandService.Trace(sp.GetRequiredService())); + extension(IServiceCollection services) { + /// + /// Registers the application service in the container + /// + /// + /// + /// + public IServiceCollection AddCommandService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, TState>() + where T : class, ICommandService + where TState : State, new() { + services.AddSingleton(); + + if (EventuousDiagnostics.Enabled) { + services.AddSingleton(sp => TracedCommandService.Trace(sp.GetRequiredService())); + } + else { + services.AddSingleton>(sp => sp.GetRequiredService()); + } + + return services; } - else { - services.AddSingleton>(sp => sp.GetRequiredService()); - } - - return services; - } - /// - /// Registers the application service in the container - /// - /// - /// Function to create an app service instance - /// - /// - /// - public static IServiceCollection AddCommandService(this IServiceCollection services, Func getService) - where T : class, ICommandService where TState : State, new() { - services.AddSingleton(getService); - - if (EventuousDiagnostics.Enabled) { - services.AddSingleton(sp => TracedCommandService.Trace(sp.GetRequiredService())); - } - else { - services.AddSingleton>(sp => sp.GetRequiredService()); + /// + /// Registers the application service in the container + /// + /// Function to create an app service instance + /// + /// + /// + public IServiceCollection AddCommandService(Func getService) + where T : class, ICommandService where TState : State, new() { + services.AddSingleton(getService); + + if (EventuousDiagnostics.Enabled) { + services.AddSingleton(sp => TracedCommandService.Trace(sp.GetRequiredService())); + } + else { + services.AddSingleton>(sp => sp.GetRequiredService()); + } + + return services; } - - return services; } } diff --git a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/StoreWithArchive.cs b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/StoreWithArchive.cs index ba6bfc29..9349c73b 100644 --- a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/StoreWithArchive.cs +++ b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/StoreWithArchive.cs @@ -8,48 +8,46 @@ namespace Microsoft.Extensions.DependencyInjection; public static class StoreWithArchiveRegistrations { - /// - /// Registers an event store service as reader, writer, and event store - /// /// - /// Implementation of that points to the hot store - /// Implementation of that points to the archive - /// - public static IServiceCollection AddEventStore + extension(IServiceCollection services) { + /// + /// Registers an event store service as reader, writer, and event store + /// + /// Implementation of that points to the hot store + /// Implementation of that points to the archive + /// + public IServiceCollection AddEventStore <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THotStore, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TArchiveStore>( - this IServiceCollection services - ) - where THotStore : class, IEventStore - where TArchiveStore : class, IEventReader { - services.TryAddSingleton(); - services.TryAddSingleton(); - services.AddSingleton(sp => new TieredEventStore(sp.GetRequiredService(), sp.GetRequiredService())); - AddReaderWriter(services); + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TArchiveStore>() + where THotStore : class, IEventStore + where TArchiveStore : class, IEventReader { + services.TryAddSingleton(); + services.TryAddSingleton(); + services.AddSingleton(sp => new TieredEventStore(sp.GetRequiredService(), sp.GetRequiredService())); + AddReaderWriter(services); - return services; - } + return services; + } - /// - /// Registers an event store service as reader, writer, and event store - /// - /// - /// Function to create an instance of that points to the hot store - /// Function to create an instance of that points to the archive store - /// Implementation of that points to the hot store - /// Implementation of that points to the archive - /// - public static IServiceCollection AddEventStore( - this IServiceCollection services, - Func getHotStore, - Func getArchive - ) - where THotStore : class, IEventStore - where TArchiveStore : class, IEventReader { - services.AddSingleton(sp => new TieredEventStore(getHotStore(sp), getArchive(sp))); - AddReaderWriter(services); + /// + /// Registers an event store service as reader, writer, and event store + /// + /// Function to create an instance of that points to the hot store + /// Function to create an instance of that points to the archive store + /// Implementation of that points to the hot store + /// Implementation of that points to the archive + /// + public IServiceCollection AddEventStore( + Func getHotStore, + Func getArchive + ) + where THotStore : class, IEventStore + where TArchiveStore : class, IEventReader { + services.AddSingleton(sp => new TieredEventStore(getHotStore(sp), getArchive(sp))); + AddReaderWriter(services); - return services; + return services; + } } static void AddReaderWriter(IServiceCollection services) { diff --git a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/Stores.cs b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/Stores.cs index da8d4801..068d593b 100644 --- a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/Stores.cs +++ b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/Stores.cs @@ -10,159 +10,154 @@ namespace Microsoft.Extensions.DependencyInjection; [PublicAPI] public static partial class ServiceCollectionExtensions { - /// - /// Registers the event reader - /// /// - /// Implementation of - /// - public static IServiceCollection AddEventReader<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this IServiceCollection services) where T : class, IEventReader { - if (EventuousDiagnostics.Enabled) { - services.TryAddSingleton(); - services.TryAddSingleton(sp => TracedEventReader.Trace(sp.GetRequiredService())); + extension(IServiceCollection services) { + /// + /// Registers the event reader + /// + /// Implementation of + /// + public IServiceCollection AddEventReader<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() where T : class, IEventReader { + if (EventuousDiagnostics.Enabled) { + services.TryAddSingleton(); + services.TryAddSingleton(sp => TracedEventReader.Trace(sp.GetRequiredService())); + } + else { services.TryAddSingleton(); } + + return services; } - else { services.TryAddSingleton(); } - return services; - } - - /// - /// Registers the event reader - /// - /// - /// Function to create an instance of - /// Implementation of - /// - public static IServiceCollection AddEventReader(this IServiceCollection services, Func getService) where T : class, IEventReader { - if (EventuousDiagnostics.Enabled) { - services.TryAddSingleton(getService); - services.TryAddSingleton(sp => TracedEventReader.Trace(sp.GetRequiredService())); + /// + /// Registers the event reader + /// + /// Function to create an instance of + /// Implementation of + /// + public IServiceCollection AddEventReader(Func getService) where T : class, IEventReader { + if (EventuousDiagnostics.Enabled) { + services.TryAddSingleton(getService); + services.TryAddSingleton(sp => TracedEventReader.Trace(sp.GetRequiredService())); + } + else { services.TryAddSingleton(getService); } + + return services; } - else { services.TryAddSingleton(getService); } - return services; - } - - /// - /// Registers the event writer - /// - /// - /// Implementation of - /// - public static IServiceCollection AddEventWriter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this IServiceCollection services) where T : class, IEventWriter { - if (EventuousDiagnostics.Enabled) { - services.TryAddSingleton(); - services.TryAddSingleton(sp => TracedEventWriter.Trace(sp.GetRequiredService())); + /// + /// Registers the event writer + /// + /// Implementation of + /// + public IServiceCollection AddEventWriter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() where T : class, IEventWriter { + if (EventuousDiagnostics.Enabled) { + services.TryAddSingleton(); + services.TryAddSingleton(sp => TracedEventWriter.Trace(sp.GetRequiredService())); + } + else { services.TryAddSingleton(); } + + return services; } - else { services.TryAddSingleton(); } - return services; - } - - /// - /// Registers the event writer - /// - /// - /// Function to create an instance of - /// Implementation of - /// - public static IServiceCollection AddEventWriter(this IServiceCollection services, Func getService) where T : class, IEventWriter { - if (EventuousDiagnostics.Enabled) { - services.TryAddSingleton(getService); - services.TryAddSingleton(sp => TracedEventWriter.Trace(sp.GetRequiredService())); + /// + /// Registers the event writer + /// + /// Function to create an instance of + /// Implementation of + /// + public IServiceCollection AddEventWriter(Func getService) where T : class, IEventWriter { + if (EventuousDiagnostics.Enabled) { + services.TryAddSingleton(getService); + services.TryAddSingleton(sp => TracedEventWriter.Trace(sp.GetRequiredService())); + } + else { services.TryAddSingleton(getService); } + + return services; } - else { services.TryAddSingleton(getService); } - - return services; - } - /// - /// Registers the event reader and writer - /// - /// - /// Implementation of and - /// - public static IServiceCollection AddEventReaderWriter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this IServiceCollection services) where T : class, IEventWriter, IEventReader { - if (EventuousDiagnostics.Enabled) { - services.TryAddSingleton(); - services.TryAddSingleton(sp => TracedEventReader.Trace(sp.GetRequiredService())); - services.TryAddSingleton(sp => TracedEventWriter.Trace(sp.GetRequiredService())); - } - else { - services.TryAddSingleton(); - services.TryAddSingleton(); + /// + /// Registers the event reader and writer + /// + /// Implementation of and + /// + public IServiceCollection AddEventReaderWriter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() where T : class, IEventWriter, IEventReader { + if (EventuousDiagnostics.Enabled) { + services.TryAddSingleton(); + services.TryAddSingleton(sp => TracedEventReader.Trace(sp.GetRequiredService())); + services.TryAddSingleton(sp => TracedEventWriter.Trace(sp.GetRequiredService())); + } + else { + services.TryAddSingleton(); + services.TryAddSingleton(); + } + + return services; } - return services; - } - - /// - /// Registers the event reader and writer implemented by one class - /// - /// - /// Function to create an instance of the class, - /// which implements both and - /// Implementation of - /// - public static IServiceCollection AddEventReaderWriter(this IServiceCollection services, Func getService) - where T : class, IEventWriter, IEventReader { - if (EventuousDiagnostics.Enabled) { - services.TryAddSingleton(getService); - services.TryAddSingleton(sp => TracedEventReader.Trace(sp.GetRequiredService())); - services.TryAddSingleton(sp => TracedEventWriter.Trace(sp.GetRequiredService())); - } - else { - services.TryAddSingleton(getService); - services.TryAddSingleton(getService); + /// + /// Registers the event reader and writer implemented by one class + /// + /// Function to create an instance of the class, + /// which implements both and + /// Implementation of + /// + public IServiceCollection AddEventReaderWriter(Func getService) + where T : class, IEventWriter, IEventReader { + if (EventuousDiagnostics.Enabled) { + services.TryAddSingleton(getService); + services.TryAddSingleton(sp => TracedEventReader.Trace(sp.GetRequiredService())); + services.TryAddSingleton(sp => TracedEventWriter.Trace(sp.GetRequiredService())); + } + else { + services.TryAddSingleton(getService); + services.TryAddSingleton(getService); + } + + return services; } - return services; - } - - /// - /// Registers an event store service as reader, writer, and event store - /// - /// - /// Implementation of - /// - public static IServiceCollection AddEventStore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this IServiceCollection services) where T : class, IEventStore { - if (EventuousDiagnostics.Enabled) { - services.TryAddSingleton(); - services.AddSingleton(sp => new TracedEventStore(sp.GetRequiredService())); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); + /// + /// Registers an event store service as reader, writer, and event store + /// + /// Implementation of + /// + public IServiceCollection AddEventStore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() where T : class, IEventStore { + if (EventuousDiagnostics.Enabled) { + services.TryAddSingleton(); + services.AddSingleton(sp => new TracedEventStore(sp.GetRequiredService())); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + } + else { + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + } + + return services; } - else { - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - } - - return services; - } - /// - /// Registers an event store service as reader, writer, and event store - /// - /// - /// Function to create an instance of the class, which implements - /// Implementation of - /// - public static IServiceCollection AddEventStore(this IServiceCollection services, Func getService) where T : class, IEventStore { - if (EventuousDiagnostics.Enabled) { - services.TryAddSingleton(getService); - services.AddSingleton(sp => new TracedEventStore(sp.GetRequiredService())); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); - } - else { - services.TryAddSingleton(getService); - services.TryAddSingleton(getService); - services.TryAddSingleton(getService); + /// + /// Registers an event store service as reader, writer, and event store + /// + /// Function to create an instance of the class, which implements + /// Implementation of + /// + public IServiceCollection AddEventStore(Func getService) where T : class, IEventStore { + if (EventuousDiagnostics.Enabled) { + services.TryAddSingleton(getService); + services.AddSingleton(sp => new TracedEventStore(sp.GetRequiredService())); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + } + else { + services.TryAddSingleton(getService); + services.TryAddSingleton(getService); + services.TryAddSingleton(getService); + } + + return services; } - - return services; } } diff --git a/src/Extensions/src/Eventuous.Subscriptions.Polly/SubscriptionBuilderExtensions.cs b/src/Extensions/src/Eventuous.Subscriptions.Polly/SubscriptionBuilderExtensions.cs index a0e31ecc..206531e8 100644 --- a/src/Extensions/src/Eventuous.Subscriptions.Polly/SubscriptionBuilderExtensions.cs +++ b/src/Extensions/src/Eventuous.Subscriptions.Polly/SubscriptionBuilderExtensions.cs @@ -8,32 +8,31 @@ namespace Eventuous.Subscriptions.Polly; [PublicAPI] public static class SubscriptionBuilderExtensions { - /// - /// Adds an event handler to the subscription, adding the specified retry policy - /// /// Subscription builder - /// Polly retry policy - /// Event handler type - /// - public static SubscriptionBuilder AddEventHandlerWithRetries<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( - this SubscriptionBuilder builder, - IAsyncPolicy retryPolicy - ) - where THandler : class, IEventHandler - => builder.AddCompositionEventHandler(h => new(h, retryPolicy)); + extension(SubscriptionBuilder builder) { + /// + /// Adds an event handler to the subscription, adding the specified retry policy + /// + /// Polly retry policy + /// Event handler type + /// + public SubscriptionBuilder AddEventHandlerWithRetries<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( + IAsyncPolicy retryPolicy + ) + where THandler : class, IEventHandler + => builder.AddCompositionEventHandler(h => new(h, retryPolicy)); - /// - /// Adds an event handler to the subscription, adding the specified retry policy - /// - /// Subscription builder - /// Function to construct the handler - /// Polly retry policy - /// Event handler type - /// - public static SubscriptionBuilder AddEventHandlerWithRetries( - this SubscriptionBuilder builder, - Func getHandler, - IAsyncPolicy retryPolicy - ) where THandler : class, IEventHandler - => builder.AddCompositionEventHandler(getHandler, h => new PollyEventHandler(h, retryPolicy)); + /// + /// Adds an event handler to the subscription, adding the specified retry policy + /// + /// Function to construct the handler + /// Polly retry policy + /// Event handler type + /// + public SubscriptionBuilder AddEventHandlerWithRetries( + Func getHandler, + IAsyncPolicy retryPolicy + ) where THandler : class, IEventHandler + => builder.AddCompositionEventHandler(getHandler, h => new PollyEventHandler(h, retryPolicy)); + } } diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/AnalyzerTestShared.cs b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/AnalyzerTestShared.cs index 30d400ca..11952ec4 100644 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/AnalyzerTestShared.cs +++ b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/AnalyzerTestShared.cs @@ -1,8 +1,8 @@ using System.Reflection; -using Eventuous.Extensions.AspNetCore.Generators; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; +using Diags = Eventuous.Extensions.AspNetCore.Generators.Diagnostics; namespace Eventuous.Tests.Extensions.AspNetCore.Analyzers; @@ -11,7 +11,7 @@ static async Task GetAnalyzerDiagnosticsAsync(Compilation compilat var withAnalyzers = compilation.WithAnalyzers([analyzer]); var diagnostics = await withAnalyzers.GetAnalyzerDiagnosticsAsync().ConfigureAwait(false); - return diagnostics.Where(d => d.Id is HttpCommandStateMismatchAnalyzer.DiagnosticId or HttpCommandStateMismatchAnalyzer.RouteDiagnosticId).ToArray(); + return diagnostics.Where(d => d.Id is Diags.DiagnosticId or Diags.RouteDiagnosticId).ToArray(); } static CSharpCompilation CreateCompilation(string source) { @@ -20,12 +20,12 @@ static CSharpCompilation CreateCompilation(string source) { var refs = new List { MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location), MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(Eventuous.State<>).Assembly.Location), + MetadataReference.CreateFromFile(typeof(State<>).Assembly.Location), MetadataReference.CreateFromFile(typeof(Eventuous.Extensions.AspNetCore.Http.CommandServiceRouteBuilder<>).Assembly.Location), MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder).Assembly.Location), // Additional ASP.NET Core references used in method signatures to enable full symbol binding MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.Builder.RouteHandlerBuilder).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.Http.HttpContext).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.Http.HttpContext).Assembly.Location) }; TryAddRef(refs, "System.Runtime"); diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/RouteMismatchTests.cs b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/RouteMismatchTests.cs index d0d437d6..a9723110 100644 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/RouteMismatchTests.cs +++ b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/RouteMismatchTests.cs @@ -1,4 +1,5 @@ using Eventuous.Extensions.AspNetCore.Generators; +using Diags = Eventuous.Extensions.AspNetCore.Generators.Diagnostics; namespace Eventuous.Tests.Extensions.AspNetCore.Analyzers; @@ -31,7 +32,7 @@ public void Map(IEndpointRouteBuilder app) { var diagnostics = await GetAnalyzerDiagnosticsAsync(compilation, analyzer); - var evta002 = diagnostics.Where(d => d.Id == HttpCommandStateMismatchAnalyzer.RouteDiagnosticId).ToArray(); + var evta002 = diagnostics.Where(d => d.Id == Diags.RouteDiagnosticId).ToArray(); await Assert.That(evta002.Length).IsEqualTo(1); await Assert.That(evta002[0].GetMessage()).Contains("ImportBookingHttp"); } diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/StateTypeMismatchTests.cs b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/StateTypeMismatchTests.cs index 3b33b3df..7f4606d4 100644 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/StateTypeMismatchTests.cs +++ b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore.Analyzers/StateTypeMismatchTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using Eventuous.Extensions.AspNetCore.Generators; +using Diags = Eventuous.Extensions.AspNetCore.Generators.Diagnostics; namespace Eventuous.Tests.Extensions.AspNetCore.Analyzers; @@ -35,7 +36,7 @@ public void Map(IEndpointRouteBuilder app) { var diagnostics = await GetAnalyzerDiagnosticsAsync(compilation, analyzer); - var evta001 = diagnostics.Where(d => d.Id == HttpCommandStateMismatchAnalyzer.DiagnosticId).ToArray(); + var evta001 = diagnostics.Where(d => d.Id == Diags.DiagnosticId).ToArray(); await Assert.That(evta001.Length).IsEqualTo(1); await Assert.That(evta001[0].GetMessage()).Contains("ImportBookingHttp3"); } diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.cs b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.cs index 9d7f76cc..646dca2f 100644 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.cs +++ b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.cs @@ -49,7 +49,7 @@ public async Task MapAggregateContractToCommandExplicitlyWithoutRouteWithGeneric [Test] public void MapAggregateContractToCommandExplicitlyWithoutRouteWithWrongGenericAttr() { - Assert.Throws(() => Act()); + Assert.Throws(Act); return; diff --git a/src/Gateway/src/Eventuous.Gateway/GatewayMetaHelper.cs b/src/Gateway/src/Eventuous.Gateway/GatewayMetaHelper.cs index 17f7720e..53c2aa17 100644 --- a/src/Gateway/src/Eventuous.Gateway/GatewayMetaHelper.cs +++ b/src/Gateway/src/Eventuous.Gateway/GatewayMetaHelper.cs @@ -8,7 +8,7 @@ namespace Eventuous.Gateway; static class GatewayMetaHelper { public static Metadata GetMeta(this GatewayMessage gatewayMessage, IMessageConsumeContext context) { var (_, _, metadata, _) = gatewayMessage; - var meta = metadata == null ? new Metadata() : new Metadata(metadata); + var meta = metadata == null ? new() : new Metadata(metadata); return meta.WithCausationId(context.MessageId); } @@ -23,32 +23,34 @@ public static Metadata GetContextMeta(IMessageConsumeContext context) { [GatewayContextItems.OriginalMessageMeta] = context.Metadata }; - return new Metadata(headers); + return new(headers); } } [PublicAPI] public static class ProducedMessageExtensions { - public static Stream? GetOriginalStream(this ProducedMessage message) - => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalStream); + extension(ProducedMessage message) { + public Stream? GetOriginalStream() + => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalStream); - public static object? GetOriginalMessage(this ProducedMessage message) - => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalMessage); + public object? GetOriginalMessage() + => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalMessage); - public static Metadata? GetOriginalMetadata(this ProducedMessage message) - => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalMessageMeta); + public Metadata? GetOriginalMetadata() + => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalMessageMeta); - public static ulong GetOriginalStreamPosition(this ProducedMessage message) - => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalStreamPosition) ?? default; + public ulong GetOriginalStreamPosition() + => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalStreamPosition) ?? default; - public static ulong GetOriginalGlobalPosition(this ProducedMessage message) - => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalGlobalPosition) ?? default; + public ulong GetOriginalGlobalPosition() + => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalGlobalPosition) ?? default; - public static string? GetOriginalMessageId(this ProducedMessage message) - => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalMessageId); + public string? GetOriginalMessageId() + => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalMessageId); - public static string? GetOriginalMessageType(this ProducedMessage message) - => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalMessageType); + public string? GetOriginalMessageType() + => message.AdditionalHeaders?.Get(GatewayContextItems.OriginalMessageType); + } } public static class GatewayContextItems { diff --git a/src/Gateway/src/Eventuous.Gateway/Registrations/GatewayRegistrations.cs b/src/Gateway/src/Eventuous.Gateway/Registrations/GatewayRegistrations.cs index f827a178..497c87af 100644 --- a/src/Gateway/src/Eventuous.Gateway/Registrations/GatewayRegistrations.cs +++ b/src/Gateway/src/Eventuous.Gateway/Registrations/GatewayRegistrations.cs @@ -14,148 +14,144 @@ namespace Microsoft.Extensions.DependencyInjection; /// [PublicAPI] public static class GatewayRegistrations { - /// - /// Registers a gateway subscription with a producer that has options. - /// /// Service collection. - /// Gateway subscription id. Must be unique across all subscription in the same application. - /// Routing and transformation function. - /// A function to configure the subscription. - /// A function to configure the subscription builder. - /// An option to wait for each produce action. - /// Subscription implementation type. - /// Subscription options type. - /// Producer implementation type. - /// Options for producing a message. - /// - public static IServiceCollection AddGateway( - this IServiceCollection services, - string subscriptionId, - RouteAndTransform routeAndTransform, - Action? configureSubscription = null, - Action>? configureBuilder = null, - bool awaitProduce = true - ) - where TSubscription : EventSubscription - where TProducer : class, IProducer - where TProduceOptions : class - where TSubscriptionOptions : SubscriptionOptions { - services.TryAddSingleton(); - services.AddHostedServiceIfSupported(); - - services.AddSubscription( - subscriptionId, - builder => { - builder.Configure(configureSubscription); - configureBuilder?.Invoke(builder); - - builder.AddEventHandler( - sp => new GatewayHandler( - new GatewayProducer(sp.GetRequiredService()), - routeAndTransform, - awaitProduce - ) - ); - } - ); - - return services; - } + extension(IServiceCollection services) { + /// + /// Registers a gateway subscription with a producer that has options. + /// + /// Gateway subscription id. Must be unique across all subscription in the same application. + /// Routing and transformation function. + /// A function to configure the subscription. + /// A function to configure the subscription builder. + /// An option to wait for each produce action. + /// Subscription implementation type. + /// Subscription options type. + /// Producer implementation type. + /// Options for producing a message. + /// + public IServiceCollection AddGateway( + string subscriptionId, + RouteAndTransform routeAndTransform, + Action? configureSubscription = null, + Action>? configureBuilder = null, + bool awaitProduce = true + ) + where TSubscription : EventSubscription + where TProducer : class, IProducer + where TProduceOptions : class + where TSubscriptionOptions : SubscriptionOptions { + services.TryAddSingleton(); + services.AddHostedServiceIfSupported(); + + services.AddSubscription( + subscriptionId, + builder => { + builder.Configure(configureSubscription); + configureBuilder?.Invoke(builder); + + builder.AddEventHandler(sp => new GatewayHandler( + new GatewayProducer(sp.GetRequiredService()), + routeAndTransform, + awaitProduce + ) + ); + } + ); + + return services; + } - /// - /// Registers a gateway subscription with a producer that has options. - /// It expects the routing and transformation function to be registered in the service collection as . - /// - /// Service collection. - /// Gateway subscription id. Must be unique across all subscription in the same application. - /// A function to configure the subscription. - /// A function to configure the subscription builder. - /// An option to wait for each produce action. - /// Subscription implementation type. - /// Subscription options type. - /// Producer implementation type. - /// Options for producing a message. - /// - public static IServiceCollection AddGateway( - this IServiceCollection services, - string subscriptionId, - Action? configureSubscription = null, - Action>? configureBuilder = null, - bool awaitProduce = true - ) - where TSubscription : EventSubscription - where TProducer : class, IProducer - where TProduceOptions : class - where TSubscriptionOptions : SubscriptionOptions { - services.TryAddSingleton(); - services.AddHostedServiceIfSupported(); - - services.AddSubscription( - subscriptionId, - builder => { - builder.Configure(configureSubscription); - configureBuilder?.Invoke(builder); - builder.AddEventHandler(GetHandler); + /// + /// Registers a gateway subscription with a producer that has options. + /// It expects the routing and transformation function to be registered in the service collection as . + /// + /// Gateway subscription id. Must be unique across all subscription in the same application. + /// A function to configure the subscription. + /// A function to configure the subscription builder. + /// An option to wait for each produce action. + /// Subscription implementation type. + /// Subscription options type. + /// Producer implementation type. + /// Options for producing a message. + /// + public IServiceCollection AddGateway( + string subscriptionId, + Action? configureSubscription = null, + Action>? configureBuilder = null, + bool awaitProduce = true + ) + where TSubscription : EventSubscription + where TProducer : class, IProducer + where TProduceOptions : class + where TSubscriptionOptions : SubscriptionOptions { + services.TryAddSingleton(); + services.AddHostedServiceIfSupported(); + + services.AddSubscription( + subscriptionId, + builder => { + builder.Configure(configureSubscription); + configureBuilder?.Invoke(builder); + builder.AddEventHandler(GetHandler); + } + ); + + return services; + + GatewayHandler GetHandler(IServiceProvider sp) { + var transform = sp.GetRequiredService>(); + var producer = sp.GetRequiredService(); + + return new(new GatewayProducer(producer), transform, awaitProduce); } - ); - - return services; - - GatewayHandler GetHandler(IServiceProvider sp) { - var transform = sp.GetRequiredService>(); - var producer = sp.GetRequiredService(); - - return new(new GatewayProducer(producer), transform, awaitProduce); } - } - /// - /// Registers a gateway subscription with a producer that has options. - /// It expects the routing and transformation function to be registered in the service collection as . - /// - /// Service collection. - /// Gateway subscription id. Must be unique across all subscription in the same application. - /// A function to configure the subscription. - /// A function to configure the subscription builder. - /// An option to wait for each produce action. - /// Subscription implementation type. - /// Subscription options type. - /// Producer implementation type. - /// Options for producing a message. - /// Message router and transformer type. - /// - public static IServiceCollection AddGateway( - this IServiceCollection services, - string subscriptionId, - Action? configureSubscription = null, - Action>? configureBuilder = null, - bool awaitProduce = true - ) - where TSubscription : EventSubscription - where TProducer : class, IProducer - where TProduceOptions : class - where TTransform : class, IGatewayTransform - where TSubscriptionOptions : SubscriptionOptions { - services.TryAddSingleton(); - services.TryAddSingleton(); - services.AddHostedServiceIfSupported(); - - services.AddSubscription( - subscriptionId, - builder => { - builder.Configure(configureSubscription); - configureBuilder?.Invoke(builder); - builder.AddEventHandler(GetHandler); + /// + /// Registers a gateway subscription with a producer that has options. + /// It expects the routing and transformation function to be registered in the service collection as . + /// + /// Gateway subscription id. Must be unique across all subscription in the same application. + /// A function to configure the subscription. + /// A function to configure the subscription builder. + /// An option to wait for each produce action. + /// Subscription implementation type. + /// Subscription options type. + /// Producer implementation type. + /// Options for producing a message. + /// Message router and transformer type. + /// + public IServiceCollection AddGateway( + string subscriptionId, + Action? configureSubscription = null, + Action>? configureBuilder = null, + bool awaitProduce = true + ) + where TSubscription : EventSubscription + where TProducer : class, IProducer + where TProduceOptions : class + where TTransform : class, IGatewayTransform + where TSubscriptionOptions : SubscriptionOptions { + services.TryAddSingleton(); + services.TryAddSingleton(); + services.AddHostedServiceIfSupported(); + + services.AddSubscription( + subscriptionId, + builder => { + builder.Configure(configureSubscription); + configureBuilder?.Invoke(builder); + builder.AddEventHandler(GetHandler); + } + ); + + return services; + + GatewayHandler GetHandler(IServiceProvider sp) { + var transform = sp.GetRequiredService(); + var producer = sp.GetRequiredService(); + + return new(new GatewayProducer(producer), transform, awaitProduce); } - ); - - return services; - - GatewayHandler GetHandler(IServiceProvider sp) { - var transform = sp.GetRequiredService(); - var producer = sp.GetRequiredService(); - - return new(new GatewayProducer(producer), transform, awaitProduce); } } } diff --git a/src/Kafka/src/Eventuous.Kafka/MetadataExtensions.cs b/src/Kafka/src/Eventuous.Kafka/MetadataExtensions.cs index 48f505d2..cd372672 100644 --- a/src/Kafka/src/Eventuous.Kafka/MetadataExtensions.cs +++ b/src/Kafka/src/Eventuous.Kafka/MetadataExtensions.cs @@ -19,20 +19,22 @@ public static Headers AsKafkaHeaders(this Metadata metadata) { return headers; } - public static Headers AddHeader(this Headers headers, string key, string? value) { - if (value != null) { - headers.Add(key, Encoding.UTF8.GetBytes(value)); + extension(Headers headers) { + public Headers AddHeader(string key, string? value) { + if (value != null) { + headers.Add(key, Encoding.UTF8.GetBytes(value)); + } + return headers; } - return headers; - } - public static Metadata AsMetadata(this Headers headers) { - var metadata = new Metadata(); + public Metadata AsMetadata() { + var metadata = new Metadata(); - foreach (var header in headers) { - metadata.Add(header.Key, Encoding.UTF8.GetString(header.GetValueBytes())); - } + foreach (var header in headers) { + metadata.Add(header.Key, Encoding.UTF8.GetString(header.GetValueBytes())); + } - return metadata; + return metadata; + } } } diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Eventuous.KurrentDB.csproj b/src/KurrentDB/src/Eventuous.KurrentDB/Eventuous.KurrentDB.csproj index 9bd4249e..f57959eb 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Eventuous.KurrentDB.csproj +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Eventuous.KurrentDB.csproj @@ -13,9 +13,6 @@ - - - diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDbProduceOptions.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDBProduceOptions.cs similarity index 89% rename from src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDbProduceOptions.cs rename to src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDBProduceOptions.cs index 4cad2f90..cb0cf2f3 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDbProduceOptions.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDBProduceOptions.cs @@ -7,7 +7,7 @@ namespace Eventuous.KurrentDB.Producers; /// Event producing options /// [PublicAPI] -public record KurrentDbProduceOptions { +public record KurrentDBProduceOptions { /// /// User credentials /// @@ -31,5 +31,5 @@ public record KurrentDbProduceOptions { /// /// Default set of options /// - public static KurrentDbProduceOptions Default { get; } = new(); + public static KurrentDBProduceOptions Default { get; } = new(); } \ No newline at end of file diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDbProducer.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDBProducer.cs similarity index 90% rename from src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDbProducer.cs rename to src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDBProducer.cs index b8162ffd..fda3263c 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDbProducer.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Producers/KurrentDBProducer.cs @@ -11,7 +11,7 @@ namespace Eventuous.KurrentDB.Producers; /// Producer for EventStoreDB /// [PublicAPI] -public class KurrentDbProducer : BaseProducer { +public class KurrentDBProducer : BaseProducer { readonly KurrentDBClient _client; readonly IEventSerializer _serializer; readonly IMetadataSerializer _metaSerializer; @@ -22,7 +22,7 @@ public class KurrentDbProducer : BaseProducer { /// EventStoreDB gRPC client /// Optional: event serializer instance /// Optional: metadata serializer instance - public KurrentDbProducer(KurrentDBClient client, IEventSerializer? serializer = null, IMetadataSerializer? metaSerializer = null) + public KurrentDBProducer(KurrentDBClient client, IEventSerializer? serializer = null, IMetadataSerializer? metaSerializer = null) : base(TracingOptions) { _client = Ensure.NotNull(client); _serializer = serializer ?? DefaultEventSerializer.Instance; @@ -35,7 +35,7 @@ public KurrentDbProducer(KurrentDBClient client, IEventSerializer? serializer = /// EventStoreDB gRPC client settings /// Optional: event serializer instance /// Optional: metadata serializer instance - public KurrentDbProducer(KurrentDBClientSettings clientSettings, IEventSerializer? serializer = null, IMetadataSerializer? metaSerializer = null) + public KurrentDBProducer(KurrentDBClientSettings clientSettings, IEventSerializer? serializer = null, IMetadataSerializer? metaSerializer = null) : this(new KurrentDBClient(Ensure.NotNull(clientSettings)), serializer, metaSerializer) { } static readonly ProducerTracingOptions TracingOptions = new() { @@ -56,10 +56,10 @@ public KurrentDbProducer(KurrentDBClientSettings clientSettings, IEventSerialize protected override async Task ProduceMessages( StreamName stream, IEnumerable messages, - KurrentDbProduceOptions? produceOptions, + KurrentDBProduceOptions? produceOptions, CancellationToken cancellationToken = default ) { - var options = produceOptions ?? KurrentDbProduceOptions.Default; + var options = produceOptions ?? KurrentDBProduceOptions.Default; foreach (var chunk in Ensure.NotNull(messages).Chunks(options.MaxAppendEventsCount)) { var chunkMessages = chunk.ToArray(); @@ -79,10 +79,10 @@ await _client.AppendToStreamAsync( ) .NoContext(); - await chunkMessages.Select(x => x.Ack()).WhenAll().NoContext(); + await chunkMessages.Select(x => x.Ack()).WhenAll().NoContext(); } catch (Exception e) { await chunkMessages - .Select(x => x.Nack("Unable to produce to EventStoreDB", e)) + .Select(x => x.Nack("Unable to produce to EventStoreDB", e)) .WhenAll() .NoContext(); } diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/StreamRevisionExtensions.cs b/src/KurrentDB/src/Eventuous.KurrentDB/StreamRevisionExtensions.cs index 6c6bac2c..0246ca3a 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/StreamRevisionExtensions.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/StreamRevisionExtensions.cs @@ -4,13 +4,6 @@ namespace Eventuous.KurrentDB; /// Internal conversions between Event Store and Eventuous types for stream positions and revisions /// public static class StreamRevisionExtensions { - /// - /// Converts to - /// - /// Stream version - /// - public static StreamState AsStreamRevision(this ExpectedStreamVersion version) => StreamState.StreamRevision((ulong)version.Value); - /// /// Converts to /// diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/AllStreamSubscription.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/AllStreamSubscription.cs index 3d5ceead..18508924 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/AllStreamSubscription.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/AllStreamSubscription.cs @@ -16,7 +16,7 @@ namespace Eventuous.KurrentDB.Subscriptions; /// Catch-up subscription for EventStoreDB, using the $all global stream /// [PublicAPI] -public class AllStreamSubscription : EventStoreCatchUpSubscriptionBase, IMeasuredSubscription { +public class AllStreamSubscription : KurrentDBCatchUpSubscriptionBase, IMeasuredSubscription { /// /// Creates EventStoreDB catch-up subscription service for $all /// @@ -103,7 +103,7 @@ Task HandleEvent(ResolvedEvent re, CancellationToken ct) => HandleInternal(CreateContext(re, ct)).AsTask(); void HandleDrop(global::KurrentDB.Client.StreamSubscription _, SubscriptionDroppedReason reason, Exception? ex) - => Dropped(EsdbMappings.AsDropReason(reason), ex); + => Dropped(KurrentDBMappings.AsDropReason(reason), ex); } [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/EventStoreCatchUpSubscriptionBase.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDBCatchUpSubscriptionBase.cs similarity index 92% rename from src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/EventStoreCatchUpSubscriptionBase.cs rename to src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDBCatchUpSubscriptionBase.cs index 7e6cf1ea..c345c839 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/EventStoreCatchUpSubscriptionBase.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDBCatchUpSubscriptionBase.cs @@ -11,7 +11,7 @@ namespace Eventuous.KurrentDB.Subscriptions; /// /// [PublicAPI] -public abstract class EventStoreCatchUpSubscriptionBase : EventSubscriptionWithCheckpoint where T : CatchUpSubscriptionOptions { +public abstract class KurrentDBCatchUpSubscriptionBase : EventSubscriptionWithCheckpoint where T : CatchUpSubscriptionOptions { /// /// Catch-up subscription base class constructor /// @@ -23,7 +23,7 @@ public abstract class EventStoreCatchUpSubscriptionBase : EventSubscriptionWi /// Optional logger factory /// Optional: event serializer instance /// Optional: metadata serializer instance - protected EventStoreCatchUpSubscriptionBase( + protected KurrentDBCatchUpSubscriptionBase( KurrentDBClient client, T options, ICheckpointStore checkpointStore, diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDbExtensions.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDBExtensions.cs similarity index 96% rename from src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDbExtensions.cs rename to src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDBExtensions.cs index c062f18a..76f5bf9b 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDbExtensions.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDBExtensions.cs @@ -5,7 +5,7 @@ namespace Eventuous.KurrentDB.Subscriptions; -static class KurrentDbExtensions { +static class KurrentDBExtensions { public static KurrentDBClientSettings GetSettings(this KurrentDBClientBase client) { var prop = typeof(KurrentDBClientBase).GetProperty("Settings", BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/EsdbMappings.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDBMappings.cs similarity index 87% rename from src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/EsdbMappings.cs rename to src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDBMappings.cs index 067b90a6..6a7248cc 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/EsdbMappings.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/KurrentDBMappings.cs @@ -1,9 +1,9 @@ // Copyright (C) Eventuous HQ OÜ. All rights reserved // Licensed under the Apache License, Version 2.0. -namespace Eventuous.KurrentDB.Subscriptions; +namespace Eventuous.KurrentDB.Subscriptions; -static class EsdbMappings { +static class KurrentDBMappings { public static DropReason AsDropReason(SubscriptionDroppedReason reason) => reason switch { SubscriptionDroppedReason.Disposed => DropReason.Stopped, diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/CatchUpSubscriptionOptions.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/CatchUpSubscriptionOptions.cs index 6cced6c8..edbc93a1 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/CatchUpSubscriptionOptions.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/CatchUpSubscriptionOptions.cs @@ -8,7 +8,7 @@ namespace Eventuous.KurrentDB.Subscriptions; /// /// Base class for catch-up subscription options /// -public record CatchUpSubscriptionOptions : EventStoreSubscriptionWithCheckpointOptions { +public record CatchUpSubscriptionOptions : KurrentDBSubscriptionWithCheckpointOptions { /// /// Number of parallel consumers. Defaults to 1. /// Don't set this value if you use partitioned subscriptions with . diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/EventStoreSubscriptionOptions.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/KurrentDBSubscriptionOptions.cs similarity index 86% rename from src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/EventStoreSubscriptionOptions.cs rename to src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/KurrentDBSubscriptionOptions.cs index e187066b..133c6a8e 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/EventStoreSubscriptionOptions.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/KurrentDBSubscriptionOptions.cs @@ -6,7 +6,7 @@ namespace Eventuous.KurrentDB.Subscriptions; /// /// Base class for EventStoreDB subscription options /// -public abstract record EventStoreSubscriptionOptions : SubscriptionOptions { +public abstract record KurrentDBSubscriptionOptions : SubscriptionOptions { /// /// User credentials /// diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/EventStoreSubscriptionWithCheckpointOptions.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/KurrentDBSubscriptionWithCheckpointOptions.cs similarity index 82% rename from src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/EventStoreSubscriptionWithCheckpointOptions.cs rename to src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/KurrentDBSubscriptionWithCheckpointOptions.cs index b18fd653..77c00b79 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/EventStoreSubscriptionWithCheckpointOptions.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/KurrentDBSubscriptionWithCheckpointOptions.cs @@ -6,7 +6,7 @@ namespace Eventuous.KurrentDB.Subscriptions; /// /// Options base record for EventStoreDB checkpoint-based subscriptions /// -public abstract record EventStoreSubscriptionWithCheckpointOptions : SubscriptionWithCheckpointOptions { +public abstract record KurrentDBSubscriptionWithCheckpointOptions : SubscriptionWithCheckpointOptions { /// /// User credentials /// diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/PersistentSubscriptionOptions.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/PersistentSubscriptionOptions.cs index 1a7d2308..c7455443 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/PersistentSubscriptionOptions.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/Options/PersistentSubscriptionOptions.cs @@ -7,7 +7,7 @@ namespace Eventuous.KurrentDB.Subscriptions; /// Base class for persistent subscription options /// [PublicAPI] -public abstract record PersistentSubscriptionOptions : EventStoreSubscriptionOptions { +public abstract record PersistentSubscriptionOptions : KurrentDBSubscriptionOptions { /// /// Native EventStoreDB settings for the subscription /// diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/PersistentSubscriptionBase.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/PersistentSubscriptionBase.cs index a57e4963..69108050 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/PersistentSubscriptionBase.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/PersistentSubscriptionBase.cs @@ -132,7 +132,7 @@ protected override async ValueTask Subscribe(CancellationToken cancellationToken return; void HandleDrop(PersistentSubscription __, SubscriptionDroppedReason reason, Exception? exception) - => Dropped(EsdbMappings.AsDropReason(reason), exception); + => Dropped(KurrentDBMappings.AsDropReason(reason), exception); async Task HandleEvent(PersistentSubscription subscription, ResolvedEvent re, int? retryCount, CancellationToken ct) { Logger.Configure(Options.SubscriptionId, LoggerFactory); diff --git a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/StreamSubscription.cs b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/StreamSubscription.cs index 9d20167a..e19733ba 100644 --- a/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/StreamSubscription.cs +++ b/src/KurrentDB/src/Eventuous.KurrentDB/Subscriptions/StreamSubscription.cs @@ -15,7 +15,7 @@ namespace Eventuous.KurrentDB.Subscriptions; /// Catch-up subscription for EventStoreDB, for a specific stream /// [PublicAPI] -public class StreamSubscription : EventStoreCatchUpSubscriptionBase, IMeasuredSubscription { +public class StreamSubscription : KurrentDBCatchUpSubscriptionBase, IMeasuredSubscription { /// /// Creates EventStoreDB catch-up subscription service for a given stream /// @@ -119,7 +119,7 @@ async Task HandleEvent(ResolvedEvent re, CancellationToken ct) { } void HandleDrop(global::KurrentDB.Client.StreamSubscription _, SubscriptionDroppedReason reason, Exception? ex) - => Dropped(EsdbMappings.AsDropReason(reason), ex); + => Dropped(KurrentDBMappings.AsDropReason(reason), ex); } [RequiresDynamicCode(AttrConstants.DynamicSerializationMessage)] diff --git a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Eventuous.Tests.KurrentDB.csproj b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Eventuous.Tests.KurrentDB.csproj index 2fac56c4..e37b5ff8 100644 --- a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Eventuous.Tests.KurrentDB.csproj +++ b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Eventuous.Tests.KurrentDB.csproj @@ -20,9 +20,6 @@ - - - diff --git a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Metrics/MetricsFixture.cs b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Metrics/MetricsFixture.cs index 58a7d439..219657ac 100644 --- a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Metrics/MetricsFixture.cs +++ b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Metrics/MetricsFixture.cs @@ -7,7 +7,7 @@ namespace Eventuous.Tests.KurrentDB.Metrics; -public class MetricsFixture : MetricsSubscriptionFixtureBase { +public class MetricsFixture : MetricsSubscriptionFixtureBase { protected override EventStoreDbContainer CreateContainer() => EsdbContainer.Create(); protected override void ConfigureSubscription(StreamSubscriptionOptions options) => options.StreamName = Stream; diff --git a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/CustomDependenciesTests.cs b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/CustomDependenciesTests.cs index caae4ad1..f5858b3a 100644 --- a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/CustomDependenciesTests.cs +++ b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/CustomDependenciesTests.cs @@ -32,7 +32,7 @@ public async Task Setup(CancellationToken cancellationToken) { services.AddLogging(b => b.AddFilter("Grpc", LogLevel.Error).AddConsole().AddTUnit(LogLevel.Debug)); services.AddKurrentDBClient(_container.GetConnectionString()); - services.AddProducer(); + services.AddProducer(); // Add NoOp store globally to make sure it's not picked up services.AddCheckpointStore(); diff --git a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/CatchUpSubscriptionFixture.cs b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/CatchUpSubscriptionFixture.cs index 78d083da..aff8f82e 100644 --- a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/CatchUpSubscriptionFixture.cs +++ b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/CatchUpSubscriptionFixture.cs @@ -14,7 +14,7 @@ public class CatchUpSubscriptionFixture? configureServices = null, LogLevel logLevel = LogLevel.Information ) : SubscriptionFixtureBase(autoStart, logLevel) - where TSubscription : EventStoreCatchUpSubscriptionBase + where TSubscription : KurrentDBCatchUpSubscriptionBase where TSubscriptionOptions : CatchUpSubscriptionOptions where TEventHandler : class, IEventHandler { protected override EventStoreDbContainer CreateContainer() => EsdbContainer.Create(); diff --git a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/LegacySubscriptionFixture.cs b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/LegacySubscriptionFixture.cs index 11792ef2..592ab9db 100644 --- a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/LegacySubscriptionFixture.cs +++ b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/LegacySubscriptionFixture.cs @@ -11,7 +11,7 @@ public abstract class LegacySubscriptionFixture : IAsyncInitializer, IAsyncDi protected StreamName Stream { get; } = new($"test-{Guid.NewGuid():N}"); protected StoreFixture StoreFixture { get; } protected T Handler { get; } - protected KurrentDbProducer Producer { get; private set; } = null!; + protected KurrentDBProducer Producer { get; private set; } = null!; protected ILogger Log { get; } protected TestCheckpointStore CheckpointStore { get; } = new(); protected StreamSubscription Subscription { get; set; } = null!; @@ -65,6 +65,7 @@ public async Task Setup() { if (autoStart) await Start(); } + [After(Test)] public async Task Teardown() { if (autoStart) await Stop(); await DisposeAsync(); diff --git a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/PersistentSubscriptionFixture.cs b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/PersistentSubscriptionFixture.cs index b7b73efd..594484dd 100644 --- a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/PersistentSubscriptionFixture.cs +++ b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/Fixtures/PersistentSubscriptionFixture.cs @@ -17,7 +17,7 @@ public class PersistentSubscriptionFixture( where TOptions : PersistentSubscriptionOptions { public StreamName Stream { get; } = new($"test-{Guid.NewGuid():N}"); public THandler Handler { get; } = handler; - public KurrentDbProducer Producer { get; private set; } = null!; + public KurrentDBProducer Producer { get; private set; } = null!; protected ILogger Log { get; set; } = null!; protected StoreFixture Fixture { get; } = new(logLevel); TSubscription Subscription { get; set; } = null!; diff --git a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/PublishAndSubscribeManyPartitionedTests.cs b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/PublishAndSubscribeManyPartitionedTests.cs index 3028d688..f1f4cd9c 100644 --- a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/PublishAndSubscribeManyPartitionedTests.cs +++ b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/PublishAndSubscribeManyPartitionedTests.cs @@ -15,7 +15,7 @@ public async Task SubscribeAndProduceMany(CancellationToken cancellationToken) { var testEvents = TestEvent.CreateMany(count); await Start(); - await Producer.Produce(Stream, testEvents, new Metadata(), cancellationToken: cancellationToken); + await Producer.Produce(Stream, testEvents, new(), cancellationToken: cancellationToken); await Handler.AssertCollection(5.Seconds(), [..testEvents]).Validate(cancellationToken); await Stop(); diff --git a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/StreamSubscriptionWithLinksTests.cs b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/StreamSubscriptionWithLinksTests.cs index 374bbd3d..f7df0127 100644 --- a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/StreamSubscriptionWithLinksTests.cs +++ b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/StreamSubscriptionWithLinksTests.cs @@ -124,7 +124,7 @@ public override ValueTask HandleEvent(IMessageConsumeContex protected override void SetupServices(IServiceCollection services) { base.SetupServices(services); - services.AddProducer(); + services.AddProducer(); services .AddSubscription( diff --git a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/SubscriptionIgnoredMessagesTests.cs b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/SubscriptionIgnoredMessagesTests.cs index db506d5f..4792cf4d 100644 --- a/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/SubscriptionIgnoredMessagesTests.cs +++ b/src/KurrentDB/test/Eventuous.Tests.KurrentDB/Subscriptions/SubscriptionIgnoredMessagesTests.cs @@ -32,7 +32,7 @@ public async Task SubscribeAndProduceManyWithIgnored(CancellationToken cancellat TypeMapper.AddType(TestEvent.TypeName); TypeMapper.AddType("ignored"); TestContext.Current?.OutputWriter.WriteLine($"Producing to {_stream}"); - await _producer.Produce(_stream, testEvents, new Metadata(), cancellationToken: cancellationToken); + await _producer.Produce(_stream, testEvents, new(), cancellationToken: cancellationToken); TestContext.Current?.OutputWriter.WriteLine("Produce complete"); TypeMapper.RemoveType(); @@ -57,7 +57,7 @@ IEnumerable Generate() { protected override void SetupServices(IServiceCollection services) { base.SetupServices(services); - services.AddProducer(); + services.AddProducer(); services.AddSubscription( _subscriptionId, diff --git a/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoCollectionExtensions.cs b/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoCollectionExtensions.cs index 5870c3c7..60d46b5b 100644 --- a/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoCollectionExtensions.cs +++ b/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoCollectionExtensions.cs @@ -8,342 +8,322 @@ namespace Eventuous.Projections.MongoDB.Tools; [PublicAPI] public static class MongoCollectionExtensions { - public static IMongoCollection GetDocumentCollection(this IMongoDatabase database, MongoCollectionName? collectionName = null) where T : Document - => GetDocumentCollection(database, collectionName ?? MongoCollectionName.For(), null); + extension(IMongoDatabase database) { + public IMongoCollection GetDocumentCollection(MongoCollectionName? collectionName = null) where T : Document + => GetDocumentCollection(database, collectionName ?? MongoCollectionName.For(), null); - public static IMongoCollection GetDocumentCollection(this IMongoDatabase database, MongoCollectionSettings settings) where T : Document - => GetDocumentCollection(database, MongoCollectionName.For(), settings); + public IMongoCollection GetDocumentCollection(MongoCollectionSettings settings) where T : Document + => GetDocumentCollection(database, MongoCollectionName.For(), settings); - public static IMongoCollection GetDocumentCollection(this IMongoDatabase database, MongoCollectionName? collectionName, MongoCollectionSettings? settings) - where T : Document - => database.GetCollection(collectionName ?? MongoCollectionName.For(), settings); + public IMongoCollection GetDocumentCollection(MongoCollectionName? collectionName, MongoCollectionSettings? settings) + where T : Document + => database.GetCollection(collectionName ?? MongoCollectionName.For(), settings); + } - public static Task DocumentExists(this IMongoCollection collection, string id, CancellationToken cancellationToken = default) where T : Document { - ArgumentException.ThrowIfNullOrWhiteSpace(id, "Document Id cannot be null or whitespace."); + extension(IMongoCollection collection) where T : Document { + public Task DocumentExists(string id, CancellationToken cancellationToken = default) { + ArgumentException.ThrowIfNullOrWhiteSpace(id, "Document Id cannot be null or whitespace."); - return collection - .Find(x => x.Id == id) - .AnyAsync(cancellationToken); - } + return collection + .Find(x => x.Id == id) + .AnyAsync(cancellationToken); + } - public static Task LoadDocument(this IMongoCollection collection, string id, CancellationToken cancellationToken = default) where T : Document { - ArgumentException.ThrowIfNullOrWhiteSpace(id, "Document Id cannot be null or whitespace."); + public Task LoadDocument(string id, CancellationToken cancellationToken = default) { + ArgumentException.ThrowIfNullOrWhiteSpace(id, "Document Id cannot be null or whitespace."); - return collection - .Find(x => x.Id == id) - .Limit(1) - .SingleOrDefaultAsync(cancellationToken)!; - } + return collection + .Find(x => x.Id == id) + .Limit(1) + .SingleOrDefaultAsync(cancellationToken)!; + } - public static Task LoadDocumentAs( - this IMongoCollection collection, - string id, - Expression> projection, - CancellationToken cancellationToken = default - ) where T : Document { - ArgumentException.ThrowIfNullOrWhiteSpace(id, "Document Id cannot be null or whitespace."); - ArgumentNullException.ThrowIfNull(projection); - - return collection - .Find(x => x.Id == id) - .Limit(1) - .Project(projection) - .SingleOrDefaultAsync(cancellationToken)!; - } + public Task LoadDocumentAs( + string id, + Expression> projection, + CancellationToken cancellationToken = default + ) { + ArgumentException.ThrowIfNullOrWhiteSpace(id, "Document Id cannot be null or whitespace."); + ArgumentNullException.ThrowIfNull(projection); + + return collection + .Find(x => x.Id == id) + .Limit(1) + .Project(projection) + .SingleOrDefaultAsync(cancellationToken)!; + } - public static Task> LoadDocuments(this IMongoCollection collection, IEnumerable ids, CancellationToken cancellationToken = default) where T : Document { - var idsList = ids.ToList(); + public Task> LoadDocuments(IEnumerable ids, CancellationToken cancellationToken = default) { + var idsList = ids.ToList(); - if (ids == null || idsList.Count == 0 || idsList.Any(IsNullOrWhiteSpace)) - throw new ArgumentException("Document ids collection cannot be empty or contain empty values", nameof(ids)); + if (ids == null || idsList.Count == 0 || idsList.Any(IsNullOrWhiteSpace)) + throw new ArgumentException("Document ids collection cannot be empty or contain empty values", nameof(ids)); - return collection - .Find(Builders.Filter.In(x => x.Id, idsList)) - .ToListAsync(cancellationToken); - } + return collection + .Find(Builders.Filter.In(x => x.Id, idsList)) + .ToListAsync(cancellationToken); + } - public static Task> LoadDocumentsAs( - this IMongoCollection collection, - IEnumerable ids, - Expression> projection, - CancellationToken cancellationToken = default - ) where T : Document { - var idsList = ids.ToList(); + public Task> LoadDocumentsAs( + IEnumerable ids, + Expression> projection, + CancellationToken cancellationToken = default + ) { + var idsList = ids.ToList(); - if (ids == null || idsList.Count == 0 || idsList.Any(IsNullOrWhiteSpace)) - throw new ArgumentException("Document ids collection cannot be empty or contain empty values", nameof(ids)); + if (ids == null || idsList.Count == 0 || idsList.Any(IsNullOrWhiteSpace)) + throw new ArgumentException("Document ids collection cannot be empty or contain empty values", nameof(ids)); - ArgumentNullException.ThrowIfNull(projection, "Projection must be specified"); + ArgumentNullException.ThrowIfNull(projection, "Projection must be specified"); - return collection - .Find(Builders.Filter.In(x => x.Id, idsList)) - .Project(projection) - .ToListAsync(cancellationToken); - } + return collection + .Find(Builders.Filter.In(x => x.Id, idsList)) + .Project(projection) + .ToListAsync(cancellationToken); + } - public static Task LoadDocumentAs( - this IMongoCollection collection, - string id, - ProjectionDefinition projection, - CancellationToken cancellationToken = default - ) where T : Document { - ArgumentException.ThrowIfNullOrWhiteSpace(id, "Document Id cannot be null or whitespace."); - ArgumentNullException.ThrowIfNull(projection); - - return collection - .Find(x => x.Id == id) - .Limit(1) - .Project(projection) - .SingleOrDefaultAsync(cancellationToken)!; - } + public Task LoadDocumentAs( + string id, + ProjectionDefinition projection, + CancellationToken cancellationToken = default + ) { + ArgumentException.ThrowIfNullOrWhiteSpace(id, "Document Id cannot be null or whitespace."); + ArgumentNullException.ThrowIfNull(projection); + + return collection + .Find(x => x.Id == id) + .Limit(1) + .Project(projection) + .SingleOrDefaultAsync(cancellationToken)!; + } - public static Task LoadDocumentAs( - this IMongoCollection collection, - string id, - Func, ProjectionDefinition> projection, - CancellationToken cancellationToken = default - ) where T : Document - => collection.LoadDocumentAs(id, projection(Builders.Projection), cancellationToken); - - public static async Task ReplaceDocument( - this IMongoCollection collection, - T document, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document { - ArgumentNullException.ThrowIfNull(document, "Document cannot be null"); - - var options = new ReplaceOptions { IsUpsert = true }; - - configure?.Invoke(options); - - return await collection.ReplaceOneAsync( - x => x.Id == document.Id, - document, - options, - cancellationToken + public Task LoadDocumentAs( + string id, + Func, ProjectionDefinition> projection, + CancellationToken cancellationToken = default ) - .NoContext(); - } + => collection.LoadDocumentAs(id, projection(Builders.Projection), cancellationToken); + + public async Task ReplaceDocument( + T document, + Action? configure, + CancellationToken cancellationToken = default + ) { + ArgumentNullException.ThrowIfNull(document, "Document cannot be null"); + + var options = new ReplaceOptions { IsUpsert = true }; + + configure?.Invoke(options); + + return await collection.ReplaceOneAsync( + x => x.Id == document.Id, + document, + options, + cancellationToken + ) + .NoContext(); + } - public static Task ReplaceDocument(this IMongoCollection collection, T document, CancellationToken cancellationToken = default) where T : Document - => collection.ReplaceDocument(document, null, cancellationToken); + public Task ReplaceDocument(T document, CancellationToken cancellationToken = default) => collection.ReplaceDocument(document, null, cancellationToken); - public static async Task DeleteDocument(this IMongoCollection collection, string id, CancellationToken cancellationToken = default) where T : Document { - ArgumentException.ThrowIfNullOrWhiteSpace(id, "Document Id cannot be null or whitespace."); + public async Task DeleteDocument(string id, CancellationToken cancellationToken = default) { + ArgumentException.ThrowIfNullOrWhiteSpace(id, "Document Id cannot be null or whitespace."); - var result = await collection.DeleteOneAsync(x => x.Id == id, cancellationToken).NoContext(); + var result = await collection.DeleteOneAsync(x => x.Id == id, cancellationToken).NoContext(); - return result.DeletedCount == 1; - } + return result.DeletedCount == 1; + } - public static async Task DeleteManyDocuments(this IMongoCollection collection, FilterDefinition filter, CancellationToken cancellationToken = default) - where T : Document { - ArgumentNullException.ThrowIfNull(filter); + public async Task DeleteManyDocuments(FilterDefinition filter, CancellationToken cancellationToken = default) { + ArgumentNullException.ThrowIfNull(filter); - var result = await collection.DeleteManyAsync(filter, cancellationToken).NoContext(); + var result = await collection.DeleteManyAsync(filter, cancellationToken).NoContext(); - return result.DeletedCount; - } + return result.DeletedCount; + } - public static Task DeleteManyDocuments(this IMongoCollection collection, BuildFilter filter, CancellationToken cancellationToken = default) where T : Document - => collection.DeleteManyDocuments(filter(Builders.Filter), cancellationToken); + public Task DeleteManyDocuments(BuildFilter filter, CancellationToken cancellationToken = default) => collection.DeleteManyDocuments(filter(Builders.Filter), cancellationToken); - public static async Task BulkUpdateDocuments( - this IMongoCollection collection, - IEnumerable documents, - BuildBulkFilter filter, - BuildBulkUpdate update, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document { - var options = new BulkWriteOptions(); + public async Task BulkUpdateDocuments( + IEnumerable documents, + BuildBulkFilter filter, + BuildBulkUpdate update, + Action? configure, + CancellationToken cancellationToken = default + ) { + var options = new BulkWriteOptions(); - configure?.Invoke(options); + configure?.Invoke(options); - var models = documents.Select(document => new UpdateOneModel( - filter(document, Builders.Filter), - update(document, Builders.Update) - ) - ); + var models = documents.Select(document => new UpdateOneModel( + filter(document, Builders.Filter), + update(document, Builders.Update) + ) + ); - var result = await collection.BulkWriteAsync(models, options, cancellationToken).NoContext(); + var result = await collection.BulkWriteAsync(models, options, cancellationToken).NoContext(); - return result.ModifiedCount; - } + return result.ModifiedCount; + } - public static async Task BulkWriteDocuments( - this IMongoCollection collection, - IEnumerable documents, - Func> write, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document { - var options = new BulkWriteOptions(); + public async Task BulkWriteDocuments( + IEnumerable documents, + Func> write, + Action? configure, + CancellationToken cancellationToken = default + ) { + var options = new BulkWriteOptions(); - configure?.Invoke(options); + configure?.Invoke(options); - return await collection.BulkWriteAsync(documents.Select(write), options, cancellationToken).NoContext(); - } + return await collection.BulkWriteAsync(documents.Select(write), options, cancellationToken).NoContext(); + } - public static Task BulkUpdateDocuments( - this IMongoCollection collection, - IEnumerable documents, - BuildBulkFilter filter, - BuildBulkUpdate update, - CancellationToken cancellationToken = default - ) where T : Document - => collection.BulkUpdateDocuments(documents, filter, update, null, cancellationToken); + public Task BulkUpdateDocuments( + IEnumerable documents, + BuildBulkFilter filter, + BuildBulkUpdate update, + CancellationToken cancellationToken = default + ) + => collection.BulkUpdateDocuments(documents, filter, update, null, cancellationToken); - public static Task CreateDocumentIndex(this IMongoCollection collection, BuildIndex index, Action? configure = null) where T : Document { - var options = new CreateIndexOptions(); + public Task CreateDocumentIndex(BuildIndex index, Action? configure = null) { + var options = new CreateIndexOptions(); - configure?.Invoke(options); + configure?.Invoke(options); - return collection.Indexes.CreateOneAsync(new CreateIndexModel(index(Builders.IndexKeys), options)); - } + return collection.Indexes.CreateOneAsync(new CreateIndexModel(index(Builders.IndexKeys), options)); + } + + public async Task CreateDocumentIndex( + BuildIndex index, + Action? configure, + CancellationToken cancellationToken + ) { + var options = new CreateIndexOptions(); - public static async Task CreateDocumentIndex( - this IMongoCollection collection, - BuildIndex index, - Action? configure, - CancellationToken cancellationToken - ) where T : Document { - var options = new CreateIndexOptions(); + configure?.Invoke(options); - configure?.Invoke(options); + try { + return await CreateIndex().NoContext(); + } catch (MongoCommandException ex) when (ex.Message.Contains("already exists")) { + // Ignore + } - try { - return await CreateIndex().NoContext(); - } catch (MongoCommandException ex) when (ex.Message.Contains("already exists")) { - // Ignore + return Empty; + + Task CreateIndex() + => collection.Indexes.CreateOneAsync(new CreateIndexModel(index(Builders.IndexKeys), options), cancellationToken: cancellationToken); } - return Empty; + public async Task UpdateDocument( + FilterDefinition filter, + UpdateDefinition update, + Action? configure, + CancellationToken cancellationToken = default + ) { + var options = new UpdateOptions { IsUpsert = true }; - Task CreateIndex() - => collection.Indexes.CreateOneAsync(new CreateIndexModel(index(Builders.IndexKeys), options), cancellationToken: cancellationToken); - } + configure?.Invoke(options); - public static async Task UpdateDocument( - this IMongoCollection collection, - FilterDefinition filter, - UpdateDefinition update, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document { - var options = new UpdateOptions { IsUpsert = true }; + await collection.UpdateOneAsync(filter, update, options, cancellationToken).NoContext(); + } - configure?.Invoke(options); + public Task UpdateDocument( + BuildFilter filter, + BuildUpdate update, + Action? configure, + CancellationToken cancellationToken = default + ) + => collection.UpdateDocument(filter(Builders.Filter), update(Builders.Update), configure, cancellationToken); - await collection.UpdateOneAsync(filter, update, options, cancellationToken).NoContext(); - } + public Task UpdateDocument( + FilterDefinition filter, + UpdateDefinition update, + CancellationToken cancellationToken = default + ) + => collection.UpdateDocument(filter, update, null, cancellationToken); - public static Task UpdateDocument( - this IMongoCollection collection, - BuildFilter filter, - BuildUpdate update, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document - => collection.UpdateDocument(filter(Builders.Filter), update(Builders.Update), configure, cancellationToken); - - public static Task UpdateDocument( - this IMongoCollection collection, - FilterDefinition filter, - UpdateDefinition update, - CancellationToken cancellationToken = default - ) where T : Document - => collection.UpdateDocument(filter, update, null, cancellationToken); - - public static Task UpdateDocument( - this IMongoCollection collection, - BuildFilter filter, - BuildUpdate update, - CancellationToken cancellationToken = default - ) where T : Document - => collection.UpdateDocument(filter(Builders.Filter), update(Builders.Update), null, cancellationToken); - - public static Task UpdateDocument( - this IMongoCollection collection, - string id, - UpdateDefinition update, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document - => IsNullOrWhiteSpace(id) - ? throw new ArgumentException("Document Id cannot be null or whitespace.", nameof(id)) - : collection.UpdateDocument(Builders.Filter.Eq(x => x.Id, id), update, configure, cancellationToken); - - /// - /// Updates a document and by default inserts a new one if no matching document by id is found. - /// - public static Task UpdateDocument( - this IMongoCollection collection, - string id, - BuildUpdate update, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document - => collection.UpdateDocument(id, update(Builders.Update), configure, cancellationToken); - - public static Task UpdateDocument( - this IMongoCollection collection, - string id, - UpdateDefinition update, - CancellationToken cancellationToken = default - ) where T : Document - => collection.UpdateDocument(id, update, null, cancellationToken); - - public static Task UpdateDocument( - this IMongoCollection collection, - string id, - BuildUpdate update, - CancellationToken cancellationToken = default - ) where T : Document - => collection.UpdateDocument(id, update, null, cancellationToken); - - public static async Task UpdateManyDocuments( - this IMongoCollection collection, - FilterDefinition filter, - UpdateDefinition update, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document { - ArgumentNullException.ThrowIfNull(filter); - ArgumentNullException.ThrowIfNull(update); - - var options = new UpdateOptions { IsUpsert = true }; - - configure?.Invoke(options); - - var result = await collection.UpdateManyAsync(filter, update, options, cancellationToken).NoContext(); - - return result.ModifiedCount; - } + public Task UpdateDocument( + BuildFilter filter, + BuildUpdate update, + CancellationToken cancellationToken = default + ) + => collection.UpdateDocument(filter(Builders.Filter), update(Builders.Update), null, cancellationToken); + + public Task UpdateDocument( + string id, + UpdateDefinition update, + Action? configure, + CancellationToken cancellationToken = default + ) + => IsNullOrWhiteSpace(id) + ? throw new ArgumentException("Document Id cannot be null or whitespace.", nameof(id)) + : collection.UpdateDocument(Builders.Filter.Eq(x => x.Id, id), update, configure, cancellationToken); + + /// + /// Updates a document and by default inserts a new one if no matching document by id is found. + /// + public Task UpdateDocument( + string id, + BuildUpdate update, + Action? configure, + CancellationToken cancellationToken = default + ) + => collection.UpdateDocument(id, update(Builders.Update), configure, cancellationToken); - public static Task UpdateManyDocuments( - this IMongoCollection collection, - BuildFilter filter, - BuildUpdate update, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document - => collection.UpdateManyDocuments(filter(Builders.Filter), update(Builders.Update), configure, cancellationToken); - - public static Task UpdateManyDocuments( - this IMongoCollection collection, - FilterDefinition filter, - UpdateDefinition update, - CancellationToken cancellationToken = default - ) where T : Document - => collection.UpdateManyDocuments(filter, update, null, cancellationToken); - - public static Task UpdateManyDocuments( - this IMongoCollection collection, - BuildFilter filter, - BuildUpdate update, - CancellationToken cancellationToken = default - ) where T : Document - => collection.UpdateManyDocuments(filter(Builders.Filter), update(Builders.Update), null, cancellationToken); + public Task UpdateDocument( + string id, + UpdateDefinition update, + CancellationToken cancellationToken = default + ) + => collection.UpdateDocument(id, update, null, cancellationToken); + + public Task UpdateDocument( + string id, + BuildUpdate update, + CancellationToken cancellationToken = default + ) + => collection.UpdateDocument(id, update, null, cancellationToken); + + public async Task UpdateManyDocuments( + FilterDefinition filter, + UpdateDefinition update, + Action? configure, + CancellationToken cancellationToken = default + ) { + ArgumentNullException.ThrowIfNull(filter); + ArgumentNullException.ThrowIfNull(update); + + var options = new UpdateOptions { IsUpsert = true }; + + configure?.Invoke(options); + + var result = await collection.UpdateManyAsync(filter, update, options, cancellationToken).NoContext(); + + return result.ModifiedCount; + } + + public Task UpdateManyDocuments( + BuildFilter filter, + BuildUpdate update, + Action? configure, + CancellationToken cancellationToken = default + ) + => collection.UpdateManyDocuments(filter(Builders.Filter), update(Builders.Update), configure, cancellationToken); + + public Task UpdateManyDocuments( + FilterDefinition filter, + UpdateDefinition update, + CancellationToken cancellationToken = default + ) + => collection.UpdateManyDocuments(filter, update, null, cancellationToken); + + public Task UpdateManyDocuments( + BuildFilter filter, + BuildUpdate update, + CancellationToken cancellationToken = default + ) + => collection.UpdateManyDocuments(filter(Builders.Filter), update(Builders.Update), null, cancellationToken); + } } diff --git a/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoCollectionName.cs b/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoCollectionName.cs index 97dafe74..6c941976 100644 --- a/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoCollectionName.cs +++ b/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoCollectionName.cs @@ -3,7 +3,7 @@ using static System.String; -namespace Eventuous.Projections.MongoDB.Tools; +namespace Eventuous.Projections.MongoDB.Tools; /// /// Convention based mongo collection name. @@ -33,7 +33,7 @@ public static MongoCollectionName For(Type type, string? prefix = null) { if (!IsNullOrWhiteSpace(prefix)) collectionName = $"{prefix}-{collectionName}"; - return new MongoCollectionName(collectionName); + return new(collectionName); } public override string ToString() => _value ?? ""; @@ -41,5 +41,5 @@ public static MongoCollectionName For(Type type, string? prefix = null) { public static implicit operator string(MongoCollectionName self) => self._value ?? ""; public static implicit operator MongoCollectionName(string value) - => IsNullOrWhiteSpace(value) ? Default : new MongoCollectionName(value); + => IsNullOrWhiteSpace(value) ? Default : new(value); } \ No newline at end of file diff --git a/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoDatabaseExtensions.cs b/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoDatabaseExtensions.cs index d92b4040..ec7b0c45 100644 --- a/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoDatabaseExtensions.cs +++ b/src/Mongo/src/Eventuous.Projections.MongoDB/Tools/MongoDatabaseExtensions.cs @@ -7,289 +7,265 @@ namespace Eventuous.Projections.MongoDB.Tools; [PublicAPI] public static class MongoDatabaseExtensions { - public static Task DocumentExists(this IMongoDatabase database, string id, CancellationToken cancellationToken = default) where T : Document - => database.GetDocumentCollection().DocumentExists(id, cancellationToken); - - public static Task LoadDocument(this IMongoDatabase database, string id, CancellationToken cancellationToken = default) where T : Document - => database.GetDocumentCollection().LoadDocument(id, cancellationToken); - - public static Task LoadDocumentAs( - this IMongoDatabase database, - string id, - Expression> projection, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().LoadDocumentAs(id, projection, cancellationToken); - - public static Task> LoadDocuments(this IMongoDatabase database, IEnumerable ids, CancellationToken cancellationToken = default) where T : Document - => database.GetDocumentCollection().LoadDocuments(ids, cancellationToken); - - public static Task> LoadDocumentsAs( - this IMongoDatabase database, - IEnumerable ids, - Expression> projection, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().LoadDocumentsAs(ids, projection, cancellationToken); - - public static Task LoadDocumentAs( - this IMongoDatabase database, - string id, - ProjectionDefinition projection, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().LoadDocumentAs(id, projection, cancellationToken); - - public static Task LoadDocumentAs( - this IMongoDatabase database, - string id, - Func, ProjectionDefinition> projection, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().LoadDocumentAs(id, projection, cancellationToken); - - public static Task StoreDocument(this IMongoDatabase database, T document, CancellationToken cancellationToken = default) where T : Document - => database.GetDocumentCollection().ReplaceDocument(document, cancellationToken); - - public static Task ReplaceDocument(this IMongoDatabase database, T document, CancellationToken cancellationToken = default) where T : Document - => database.GetDocumentCollection().ReplaceDocument(document, cancellationToken); - - public static Task ReplaceDocument( - this IMongoDatabase database, - T document, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().ReplaceDocument(document, configure, cancellationToken); - - public static Task UpdateDocument( - this IMongoDatabase database, - FilterDefinition filter, - UpdateDefinition update, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().UpdateDocument(filter, update, configure, cancellationToken); - - public static Task UpdateDocument( - this IMongoDatabase database, - BuildFilter filter, - BuildUpdate update, - Action? configure, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().UpdateDocument(filter, update, configure, cancellationToken); - - public static Task UpdateDocument( - this IMongoDatabase database, - FilterDefinition filter, - UpdateDefinition update, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().UpdateDocument(filter, update, cancellationToken); - - public static Task UpdateDocument( - this IMongoDatabase database, - BuildFilter filter, - BuildUpdate update, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().UpdateDocument(filter, update, cancellationToken); - - public static Task UpdateDocument( - this IMongoDatabase database, - string id, - BuildUpdate update, - Action configure, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().UpdateDocument(id, update, configure, cancellationToken); - - public static Task UpdateDocument(this IMongoDatabase database, string id, BuildUpdate update, CancellationToken cancellationToken = default) where T : Document - => database.GetDocumentCollection().UpdateDocument(id, update, cancellationToken); - - public static Task UpdateDocument(this IMongoDatabase database, string id, UpdateDefinition update, CancellationToken cancellationToken = default) where T : Document - => database.GetDocumentCollection().UpdateDocument(id, update, cancellationToken); - - public static Task UpdateManyDocuments( - this IMongoDatabase database, - BuildFilter filter, - BuildUpdate update, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().UpdateManyDocuments(filter, update, cancellationToken); - - public static Task UpdateManyDocuments( - this IMongoDatabase database, - FilterDefinition filter, - UpdateDefinition update, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().UpdateManyDocuments(filter, update, cancellationToken); - - public static Task UpdateManyDocuments( - this IMongoDatabase database, - BuildFilter filter, - BuildUpdate update, - Action configure, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().UpdateManyDocuments(filter, update, configure, cancellationToken); - - public static Task UpdateManyDocuments( - this IMongoDatabase database, - FilterDefinition filter, - UpdateDefinition update, - Action configure, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().UpdateManyDocuments(filter, update, configure, cancellationToken); - - public static Task DeleteDocument(this IMongoDatabase database, string id, CancellationToken cancellationToken = default) where T : Document - => database.GetDocumentCollection().DeleteDocument(id, cancellationToken); - - public static Task DeleteManyDocuments(this IMongoDatabase database, BuildFilter filter, CancellationToken cancellationToken = default) where T : Document - => database.GetDocumentCollection().DeleteManyDocuments(filter, cancellationToken); - - public static Task DeleteManyDocuments(this IMongoDatabase database, FilterDefinition filter, CancellationToken cancellationToken = default) where T : Document - => database.GetDocumentCollection().DeleteManyDocuments(filter, cancellationToken); - - public static Task BulkUpdateDocuments( - this IMongoDatabase database, - IEnumerable documents, - BuildBulkFilter filter, - BuildBulkUpdate update, - Action configure, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().BulkUpdateDocuments(documents, filter, update, configure, cancellationToken); - - public static Task BulkUpdateDocuments( - this IMongoDatabase database, - IEnumerable documents, - BuildBulkFilter filter, - BuildBulkUpdate update, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection().BulkUpdateDocuments(documents, filter, update, cancellationToken); - - public static Task DocumentExists( - this IMongoDatabase database, - string id, - MongoCollectionName collectionName, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName).DocumentExists(id, cancellationToken); - - public static Task LoadDocument( - this IMongoDatabase database, - string id, - MongoCollectionName collectionName, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName).LoadDocument(id, cancellationToken); - - public static Task LoadDocumentAs( - this IMongoDatabase database, - string id, - Expression> projection, - MongoCollectionName collectionName, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName).LoadDocumentAs(id, projection, cancellationToken); - - public static Task LoadDocumentAs( - this IMongoDatabase database, - string id, - ProjectionDefinition projection, - MongoCollectionName collectionName, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName).LoadDocumentAs(id, projection, cancellationToken); - - public static Task LoadDocumentAs( - this IMongoDatabase database, - string id, - Func, ProjectionDefinition> projection, - MongoCollectionName collectionName, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName) - .LoadDocumentAs(id, projection, cancellationToken); - - public static Task UpdateDocument( - this IMongoDatabase database, - string id, - MongoCollectionName collectionName, - BuildUpdate update, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName).UpdateDocument(id, update, cancellationToken); - - public static Task UpdateDocument( - this IMongoDatabase database, - string id, - MongoCollectionName collectionName, - UpdateDefinition update, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName).UpdateDocument(id, update, cancellationToken); - - public static Task UpdateManyDocuments( - this IMongoDatabase database, - MongoCollectionName collectionName, - BuildFilter filter, - BuildUpdate update, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName).UpdateManyDocuments(filter, update, cancellationToken); - - public static Task UpdateManyDocuments( - this IMongoDatabase database, - MongoCollectionName collectionName, - FilterDefinition filter, - UpdateDefinition update, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName).UpdateManyDocuments(filter, update, cancellationToken); - - public static Task DeleteDocument( - this IMongoDatabase database, - string id, - MongoCollectionName collectionName, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName).DeleteDocument(id, cancellationToken); - - public static Task DeleteManyDocuments( - this IMongoDatabase database, - MongoCollectionName collectionName, - BuildFilter filter, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName).DeleteManyDocuments(filter, cancellationToken); - - public static Task DeleteManyDocuments( - this IMongoDatabase database, - MongoCollectionName collectionName, - FilterDefinition filter, - CancellationToken cancellationToken = default - ) where T : Document - => database.GetDocumentCollection(collectionName).DeleteManyDocuments(filter, cancellationToken); - - public static IQueryable AsQueryable(this IMongoDatabase database, MongoCollectionName collectionName, Action? configure = null) - where T : Document { - var options = new AggregateOptions(); - configure?.Invoke(options); - return database.GetDocumentCollection(collectionName).AsQueryable(options); + extension(IMongoDatabase database) { + public Task DocumentExists(string id, CancellationToken cancellationToken = default) where T : Document + => database.GetDocumentCollection().DocumentExists(id, cancellationToken); + + public Task LoadDocument(string id, CancellationToken cancellationToken = default) where T : Document + => database.GetDocumentCollection().LoadDocument(id, cancellationToken); + + public Task LoadDocumentAs( + string id, + Expression> projection, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().LoadDocumentAs(id, projection, cancellationToken); + + public Task> LoadDocuments(IEnumerable ids, CancellationToken cancellationToken = default) where T : Document + => database.GetDocumentCollection().LoadDocuments(ids, cancellationToken); + + public Task> LoadDocumentsAs( + IEnumerable ids, + Expression> projection, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().LoadDocumentsAs(ids, projection, cancellationToken); + + public Task LoadDocumentAs( + string id, + ProjectionDefinition projection, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().LoadDocumentAs(id, projection, cancellationToken); + + public Task LoadDocumentAs( + string id, + Func, ProjectionDefinition> projection, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().LoadDocumentAs(id, projection, cancellationToken); + + public Task StoreDocument(T document, CancellationToken cancellationToken = default) where T : Document + => database.GetDocumentCollection().ReplaceDocument(document, cancellationToken); + + public Task ReplaceDocument(T document, CancellationToken cancellationToken = default) where T : Document + => database.GetDocumentCollection().ReplaceDocument(document, cancellationToken); + + public Task ReplaceDocument( + T document, + Action? configure, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().ReplaceDocument(document, configure, cancellationToken); + + public Task UpdateDocument( + FilterDefinition filter, + UpdateDefinition update, + Action? configure, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().UpdateDocument(filter, update, configure, cancellationToken); + + public Task UpdateDocument( + BuildFilter filter, + BuildUpdate update, + Action? configure, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().UpdateDocument(filter, update, configure, cancellationToken); + + public Task UpdateDocument( + FilterDefinition filter, + UpdateDefinition update, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().UpdateDocument(filter, update, cancellationToken); + + public Task UpdateDocument( + BuildFilter filter, + BuildUpdate update, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().UpdateDocument(filter, update, cancellationToken); + + public Task UpdateDocument( + string id, + BuildUpdate update, + Action configure, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().UpdateDocument(id, update, configure, cancellationToken); + + public Task UpdateDocument(string id, BuildUpdate update, CancellationToken cancellationToken = default) where T : Document + => database.GetDocumentCollection().UpdateDocument(id, update, cancellationToken); + + public Task UpdateDocument(string id, UpdateDefinition update, CancellationToken cancellationToken = default) where T : Document + => database.GetDocumentCollection().UpdateDocument(id, update, cancellationToken); + + public Task UpdateManyDocuments( + BuildFilter filter, + BuildUpdate update, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().UpdateManyDocuments(filter, update, cancellationToken); + + public Task UpdateManyDocuments( + FilterDefinition filter, + UpdateDefinition update, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().UpdateManyDocuments(filter, update, cancellationToken); + + public Task UpdateManyDocuments( + BuildFilter filter, + BuildUpdate update, + Action configure, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().UpdateManyDocuments(filter, update, configure, cancellationToken); + + public Task UpdateManyDocuments( + FilterDefinition filter, + UpdateDefinition update, + Action configure, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().UpdateManyDocuments(filter, update, configure, cancellationToken); + + public Task DeleteDocument(string id, CancellationToken cancellationToken = default) where T : Document + => database.GetDocumentCollection().DeleteDocument(id, cancellationToken); + + public Task DeleteManyDocuments(BuildFilter filter, CancellationToken cancellationToken = default) where T : Document + => database.GetDocumentCollection().DeleteManyDocuments(filter, cancellationToken); + + public Task DeleteManyDocuments(FilterDefinition filter, CancellationToken cancellationToken = default) where T : Document + => database.GetDocumentCollection().DeleteManyDocuments(filter, cancellationToken); + + public Task BulkUpdateDocuments( + IEnumerable documents, + BuildBulkFilter filter, + BuildBulkUpdate update, + Action configure, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().BulkUpdateDocuments(documents, filter, update, configure, cancellationToken); + + public Task BulkUpdateDocuments( + IEnumerable documents, + BuildBulkFilter filter, + BuildBulkUpdate update, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection().BulkUpdateDocuments(documents, filter, update, cancellationToken); + + public Task DocumentExists( + string id, + MongoCollectionName collectionName, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName).DocumentExists(id, cancellationToken); + + public Task LoadDocument( + string id, + MongoCollectionName collectionName, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName).LoadDocument(id, cancellationToken); + + public Task LoadDocumentAs( + string id, + Expression> projection, + MongoCollectionName collectionName, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName).LoadDocumentAs(id, projection, cancellationToken); + + public Task LoadDocumentAs( + string id, + ProjectionDefinition projection, + MongoCollectionName collectionName, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName).LoadDocumentAs(id, projection, cancellationToken); + + public Task LoadDocumentAs( + string id, + Func, ProjectionDefinition> projection, + MongoCollectionName collectionName, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName) + .LoadDocumentAs(id, projection, cancellationToken); + + public Task UpdateDocument( + string id, + MongoCollectionName collectionName, + BuildUpdate update, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName).UpdateDocument(id, update, cancellationToken); + + public Task UpdateDocument( + string id, + MongoCollectionName collectionName, + UpdateDefinition update, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName).UpdateDocument(id, update, cancellationToken); + + public Task UpdateManyDocuments( + MongoCollectionName collectionName, + BuildFilter filter, + BuildUpdate update, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName).UpdateManyDocuments(filter, update, cancellationToken); + + public Task UpdateManyDocuments( + MongoCollectionName collectionName, + FilterDefinition filter, + UpdateDefinition update, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName).UpdateManyDocuments(filter, update, cancellationToken); + + public Task DeleteDocument( + string id, + MongoCollectionName collectionName, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName).DeleteDocument(id, cancellationToken); + + public Task DeleteManyDocuments( + MongoCollectionName collectionName, + BuildFilter filter, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName).DeleteManyDocuments(filter, cancellationToken); + + public Task DeleteManyDocuments( + MongoCollectionName collectionName, + FilterDefinition filter, + CancellationToken cancellationToken = default + ) where T : Document + => database.GetDocumentCollection(collectionName).DeleteManyDocuments(filter, cancellationToken); + + public IQueryable AsQueryable(MongoCollectionName collectionName, Action? configure = null) + where T : Document { + var options = new AggregateOptions(); + configure?.Invoke(options); + + return database.GetDocumentCollection(collectionName).AsQueryable(options); + } + + public IQueryable AsQueryable(Action? configure = null) where T : Document { + var options = new AggregateOptions(); + configure?.Invoke(options); + + return database.GetDocumentCollection().AsQueryable(options); + } + + public Task CreateDocumentIndex(BuildIndex index, Action? configure = null) where T : Document + => database.GetDocumentCollection().CreateDocumentIndex(index, configure); } - - public static IQueryable AsQueryable(this IMongoDatabase database, Action? configure = null) where T : Document { - var options = new AggregateOptions(); - configure?.Invoke(options); - return database.GetDocumentCollection().AsQueryable(options); - } - - public static Task CreateDocumentIndex(this IMongoDatabase database, BuildIndex index, Action? configure = null) where T : Document - => database.GetDocumentCollection().CreateDocumentIndex(index, configure); } diff --git a/src/Postgres/src/Eventuous.Postgresql/Extensions/PostgresExtensions.cs b/src/Postgres/src/Eventuous.Postgresql/Extensions/PostgresExtensions.cs index f1281c85..b7a4503f 100644 --- a/src/Postgres/src/Eventuous.Postgresql/Extensions/PostgresExtensions.cs +++ b/src/Postgres/src/Eventuous.Postgresql/Extensions/PostgresExtensions.cs @@ -14,13 +14,15 @@ public static NpgsqlCommand GetCommand(this NpgsqlConnection connection, string return cmd; } - public static NpgsqlCommand Add(this NpgsqlCommand command, string name, NpgsqlDbType type, object value) { - command.Parameters.AddWithValue(name, type, value); - return command; - } + extension(NpgsqlCommand command) { + public NpgsqlCommand Add(string name, NpgsqlDbType type, object value) { + command.Parameters.AddWithValue(name, type, value); + return command; + } - public static NpgsqlCommand Add(this NpgsqlCommand command, string name, object value) { - command.Parameters.AddWithValue(name, value); - return command; + public NpgsqlCommand Add(string name, object value) { + command.Parameters.AddWithValue(name, value); + return command; + } } } diff --git a/src/Postgres/src/Eventuous.Postgresql/Extensions/RegistrationExtensions.cs b/src/Postgres/src/Eventuous.Postgresql/Extensions/RegistrationExtensions.cs index 2871995b..7909e50f 100644 --- a/src/Postgres/src/Eventuous.Postgresql/Extensions/RegistrationExtensions.cs +++ b/src/Postgres/src/Eventuous.Postgresql/Extensions/RegistrationExtensions.cs @@ -14,129 +14,126 @@ namespace Microsoft.Extensions.DependencyInjection; public static class ServiceCollectionExtensions { - /// - /// Adds PostgreSQL event store and the necessary schema to the DI container. - /// /// Service collection - /// Connection string - /// Schema name - /// Set to true if you want the schema to be created on startup - /// Optional: function to configure the data source builder - /// Optional: lifetime of the connection, default is transient - /// Optional> lifetime of the data source, default is singleton - /// Services collection - // ReSharper disable once UnusedMethodReturnValue.Global - public static IServiceCollection AddEventuousPostgres( - this IServiceCollection services, - string connectionString, - string schema = Schema.DefaultSchema, - bool initializeDatabase = false, - Action? configureBuilder = null, - ServiceLifetime connectionLifetime = ServiceLifetime.Transient, - ServiceLifetime dataSourceLifetime = ServiceLifetime.Singleton - ) { - var options = new PostgresStoreOptions { - Schema = schema, - ConnectionString = connectionString, - InitializeDatabase = initializeDatabase - }; + extension(IServiceCollection services) { + /// + /// Adds PostgreSQL event store and the necessary schema to the DI container. + /// + /// Connection string + /// Schema name + /// Set to true if you want the schema to be created on startup + /// Optional: function to configure the data source builder + /// Optional: lifetime of the connection, default is transient + /// Optional> lifetime of the data source, default is singleton + /// Services collection + // ReSharper disable once UnusedMethodReturnValue.Global + public IServiceCollection AddEventuousPostgres( + string connectionString, + string schema = Schema.DefaultSchema, + bool initializeDatabase = false, + Action? configureBuilder = null, + ServiceLifetime connectionLifetime = ServiceLifetime.Transient, + ServiceLifetime dataSourceLifetime = ServiceLifetime.Singleton + ) { + var options = new PostgresStoreOptions { + Schema = schema, + ConnectionString = connectionString, + InitializeDatabase = initializeDatabase + }; - services.AddNpgsqlDataSourceCore( - _ => connectionString, - (sp, builder) => { - builder.MapComposite(Schema.GetStreamMessageTypeName(schema)); - configureBuilder?.Invoke(sp, builder); - }, - connectionLifetime, - dataSourceLifetime - ); - services.AddSingleton(options); - services.AddSingleton(); - services.AddHostedService(); + services.AddNpgsqlDataSourceCore( + _ => connectionString, + (sp, builder) => { + builder.MapComposite(Schema.GetStreamMessageTypeName(schema)); + configureBuilder?.Invoke(sp, builder); + }, + connectionLifetime, + dataSourceLifetime + ); + services.AddSingleton(options); + services.AddSingleton(); + services.AddHostedService(); - return services; - } + return services; + } - /// - /// Adds PostgreSQL event store and the necessary schema to the DI container. - /// - /// Service collection - /// Configuration section for PostgreSQL options - /// Optional: function to configure the data source builder - /// Optional: lifetime of the connection, default is transient - /// Optional> lifetime of the data source, default is singleton - /// Services collection - // ReSharper disable once UnusedMethodReturnValue.Global - public static IServiceCollection AddEventuousPostgres( - this IServiceCollection services, - IConfiguration config, - Action? configureBuilder = null, - ServiceLifetime connectionLifetime = ServiceLifetime.Transient, - ServiceLifetime dataSourceLifetime = ServiceLifetime.Singleton - ) { - services.Configure(config); - services.AddSingleton(sp => sp.GetRequiredService>().Value); + /// + /// Adds PostgreSQL event store and the necessary schema to the DI container. + /// + /// Configuration section for PostgreSQL options + /// Optional: function to configure the data source builder + /// Optional: lifetime of the connection, default is transient + /// Optional> lifetime of the data source, default is singleton + /// Services collection + // ReSharper disable once UnusedMethodReturnValue.Global + public IServiceCollection AddEventuousPostgres( + IConfiguration config, + Action? configureBuilder = null, + ServiceLifetime connectionLifetime = ServiceLifetime.Transient, + ServiceLifetime dataSourceLifetime = ServiceLifetime.Singleton + ) { + services.Configure(config); + services.AddSingleton(sp => sp.GetRequiredService>().Value); - services.AddNpgsqlDataSourceCore( - sp => Ensure.NotEmptyString(sp.GetRequiredService().ConnectionString), - (sp, builder) => { - var options = sp.GetRequiredService(); - builder.MapComposite(Schema.GetStreamMessageTypeName(options.Schema)); - configureBuilder?.Invoke(sp, builder); - }, - connectionLifetime, - dataSourceLifetime - ); + services.AddNpgsqlDataSourceCore( + sp => Ensure.NotEmptyString(sp.GetRequiredService().ConnectionString), + (sp, builder) => { + var options = sp.GetRequiredService(); + builder.MapComposite(Schema.GetStreamMessageTypeName(options.Schema)); + configureBuilder?.Invoke(sp, builder); + }, + connectionLifetime, + dataSourceLifetime + ); - services.AddSingleton(); - services.AddHostedService(); + services.AddSingleton(); + services.AddHostedService(); - return services; - } + return services; + } - static void AddNpgsqlDataSourceCore( - this IServiceCollection services, - Func getConnectionString, - Action? configureDataSource, - ServiceLifetime connectionLifetime, - ServiceLifetime dataSourceLifetime - ) { - services.TryAdd( - new ServiceDescriptor( - typeof(NpgsqlDataSource), - sp => { - var dataSourceBuilder = new NpgsqlDataSourceBuilder(getConnectionString(sp)); - dataSourceBuilder.UseLoggerFactory(sp.GetService()); - configureDataSource?.Invoke(sp, dataSourceBuilder); + void AddNpgsqlDataSourceCore( + Func getConnectionString, + Action? configureDataSource, + ServiceLifetime connectionLifetime, + ServiceLifetime dataSourceLifetime + ) { + services.TryAdd( + new ServiceDescriptor( + typeof(NpgsqlDataSource), + sp => { + var dataSourceBuilder = new NpgsqlDataSourceBuilder(getConnectionString(sp)); + dataSourceBuilder.UseLoggerFactory(sp.GetService()); + configureDataSource?.Invoke(sp, dataSourceBuilder); - return dataSourceBuilder.Build(); - }, - dataSourceLifetime - ) - ); + return dataSourceBuilder.Build(); + }, + dataSourceLifetime + ) + ); - services.TryAdd( - new ServiceDescriptor(typeof(NpgsqlConnection), sp => sp.GetRequiredService().CreateConnection(), connectionLifetime) - ); - services.TryAdd(new ServiceDescriptor(typeof(DbDataSource), sp => sp.GetRequiredService(), dataSourceLifetime)); - services.TryAdd(new ServiceDescriptor(typeof(DbConnection), sp => sp.GetRequiredService(), connectionLifetime)); - } + services.TryAdd( + new ServiceDescriptor(typeof(NpgsqlConnection), sp => sp.GetRequiredService().CreateConnection(), connectionLifetime) + ); + services.TryAdd(new ServiceDescriptor(typeof(DbDataSource), sp => sp.GetRequiredService(), dataSourceLifetime)); + services.TryAdd(new ServiceDescriptor(typeof(DbConnection), sp => sp.GetRequiredService(), connectionLifetime)); + } - public static IServiceCollection AddPostgresCheckpointStore(this IServiceCollection services) { - return services.AddCheckpointStore( - sp => { - var ds = sp.GetRequiredService(); - var loggerFactory = sp.GetService(); - var storeOptions = sp.GetService(); - var checkpointStoreOptions = sp.GetService>(); + public IServiceCollection AddPostgresCheckpointStore() { + return services.AddCheckpointStore(sp => { + var ds = sp.GetRequiredService(); + var loggerFactory = sp.GetService(); + var storeOptions = sp.GetService(); + var checkpointStoreOptions = sp.GetService>(); - var schema = storeOptions?.Schema is not null and not Schema.DefaultSchema - && checkpointStoreOptions?.Value.Schema == Schema.DefaultSchema - ? storeOptions.Schema - : checkpointStoreOptions?.Value.Schema ?? Schema.DefaultSchema; + var schema = storeOptions?.Schema is not null and not Schema.DefaultSchema + && checkpointStoreOptions?.Value.Schema == Schema.DefaultSchema + ? storeOptions.Schema + : checkpointStoreOptions?.Value.Schema ?? Schema.DefaultSchema; - return new(ds, schema, loggerFactory); - } - ); + return new(ds, schema, loggerFactory); + } + ); + } } } diff --git a/src/Postgres/src/Eventuous.Postgresql/Subscriptions/PostgresCheckpointStore.cs b/src/Postgres/src/Eventuous.Postgresql/Subscriptions/PostgresCheckpointStore.cs index d74dcbe5..0bda0bfc 100644 --- a/src/Postgres/src/Eventuous.Postgresql/Subscriptions/PostgresCheckpointStore.cs +++ b/src/Postgres/src/Eventuous.Postgresql/Subscriptions/PostgresCheckpointStore.cs @@ -70,7 +70,7 @@ public async ValueTask GetLastCheckpoint(string checkpointId, Cancel if (!await reader.ReadAsync(cancellationToken).NoContext()) return (Checkpoint.Empty(checkpointId), false); var hasPosition = !reader.IsDBNull(0); - checkpoint = hasPosition ? new Checkpoint(checkpointId, (ulong?)reader.GetInt64(0)) : Checkpoint.Empty(checkpointId); + checkpoint = hasPosition ? new(checkpointId, (ulong?)reader.GetInt64(0)) : Checkpoint.Empty(checkpointId); Logger.Current.CheckpointLoaded(this, checkpoint); return (checkpoint, true); diff --git a/src/Postgres/src/Eventuous.Postgresql/Subscriptions/PostgresSubscriptionBase.cs b/src/Postgres/src/Eventuous.Postgresql/Subscriptions/PostgresSubscriptionBase.cs index 6fcd4507..d2c2dd33 100644 --- a/src/Postgres/src/Eventuous.Postgresql/Subscriptions/PostgresSubscriptionBase.cs +++ b/src/Postgres/src/Eventuous.Postgresql/Subscriptions/PostgresSubscriptionBase.cs @@ -63,7 +63,7 @@ protected override async ValueTask HandleGapTimeout(long gapPosition, long curre } protected override bool ShouldSkipEvent(PersistedEvent evt) - => evt.StreamName == PostgresSubscriptionConstants.TombstoneStream && evt.MessageType == PostgresSubscriptionConstants.TombstoneMessageType; + => evt is { StreamName: PostgresSubscriptionConstants.TombstoneStream, MessageType: PostgresSubscriptionConstants.TombstoneMessageType }; } public static class PostgresSubscriptionConstants { diff --git a/src/Postgres/test/Eventuous.Tests.Postgres/Projections/ProjectorTests.cs b/src/Postgres/test/Eventuous.Tests.Postgres/Projections/ProjectorTests.cs index cf353aaf..860eeb01 100644 --- a/src/Postgres/test/Eventuous.Tests.Postgres/Projections/ProjectorTests.cs +++ b/src/Postgres/test/Eventuous.Tests.Postgres/Projections/ProjectorTests.cs @@ -10,7 +10,7 @@ namespace Eventuous.Tests.Postgres.Projections; -public class ProjectorTests() { +public class ProjectorTests { readonly SubscriptionFixture _fixture = new(_ => { }); const string Schema = """ diff --git a/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/TombstonesCreationTest.cs b/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/TombstonesCreationTest.cs index 6fea54f9..c8e4a7e4 100644 --- a/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/TombstonesCreationTest.cs +++ b/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/TombstonesCreationTest.cs @@ -13,7 +13,7 @@ public class TombstonesCreationTest() : SubscriptionTestBase(Fixture) { [Test] public async Task ShouldCreateTombstones(CancellationToken cancellationToken) { - var streamName = new StreamName($"test-stream"); + var streamName = new StreamName("test-stream"); // create 2 events await Fixture.AppendEvents(streamName, Fixture.CreateEvents(2).Cast().ToArray(), ExpectedStreamVersion.NoStream); diff --git a/src/Redis/src/Eventuous.Redis/Subscriptions/RedisCheckpointStore.cs b/src/Redis/src/Eventuous.Redis/Subscriptions/RedisCheckpointStore.cs index b6681100..f9a43d92 100644 --- a/src/Redis/src/Eventuous.Redis/Subscriptions/RedisCheckpointStore.cs +++ b/src/Redis/src/Eventuous.Redis/Subscriptions/RedisCheckpointStore.cs @@ -11,7 +11,7 @@ public class RedisCheckpointStore(GetRedisDatabase getDatabase, ILoggerFactory? public async ValueTask GetLastCheckpoint(string checkpointId, CancellationToken cancellationToken) { Logger.ConfigureIfNull(checkpointId, loggerFactory); var position = await getDatabase().StringGetAsync(checkpointId).NoContext(); - var checkpoint = position.IsNull ? Checkpoint.Empty(checkpointId) : new Checkpoint(checkpointId, Convert.ToUInt64(position)); + var checkpoint = position.IsNull ? Checkpoint.Empty(checkpointId) : new(checkpointId, Convert.ToUInt64(position)); Logger.Current.CheckpointLoaded(this, checkpoint); return checkpoint; } diff --git a/src/Redis/src/Eventuous.Redis/Subscriptions/RedisSubscriptionBase.cs b/src/Redis/src/Eventuous.Redis/Subscriptions/RedisSubscriptionBase.cs index 149b59f1..3d3f5bc6 100644 --- a/src/Redis/src/Eventuous.Redis/Subscriptions/RedisSubscriptionBase.cs +++ b/src/Redis/src/Eventuous.Redis/Subscriptions/RedisSubscriptionBase.cs @@ -90,9 +90,7 @@ MessageConsumeContext ToConsumeContext(ReceivedEvent evt, CancellationToken canc (ulong)evt.StreamPosition ); - var meta = (evt.JsonMetadata == null) - ? new Metadata() - : _metaSerializer.Deserialize(Encoding.UTF8.GetBytes(evt.JsonMetadata)); + var meta = (evt.JsonMetadata == null) ? new() : _metaSerializer.Deserialize(Encoding.UTF8.GetBytes(evt.JsonMetadata)); return AsContext(evt, data, meta, cancellationToken); } diff --git a/src/Redis/test/Eventuous.Tests.Redis/Fixtures/SubscriptionFixture.cs b/src/Redis/test/Eventuous.Tests.Redis/Fixtures/SubscriptionFixture.cs index 3e305e59..34267949 100644 --- a/src/Redis/test/Eventuous.Tests.Redis/Fixtures/SubscriptionFixture.cs +++ b/src/Redis/test/Eventuous.Tests.Redis/Fixtures/SubscriptionFixture.cs @@ -19,7 +19,7 @@ namespace Eventuous.Tests.Redis.Fixtures; IMessageSubscription Subscription { get; set; } = null!; public SubscriptionFixture(bool subscribeToAll, LogLevel logLevel = LogLevel.Trace) { - Handler = new T(); + Handler = new(); _subscribeToAll = subscribeToAll; Stream = new(Guid.NewGuid().ToString()); LoggerFactory = LoggingExtensions.GetLoggerFactory(logLevel); diff --git a/src/Redis/test/Eventuous.Tests.Redis/Store/Helpers.cs b/src/Redis/test/Eventuous.Tests.Redis/Store/Helpers.cs index 0d0fe3da..ec874b80 100644 --- a/src/Redis/test/Eventuous.Tests.Redis/Store/Helpers.cs +++ b/src/Redis/test/Eventuous.Tests.Redis/Store/Helpers.cs @@ -18,21 +18,22 @@ public static IEnumerable CreateEvents(int count) { static BookingImported ToEvent(ImportBooking cmd) => new(cmd.RoomId, cmd.Price, cmd.CheckIn, cmd.CheckOut); - public static Task AppendEvents( - this IntegrationFixture fixture, - StreamName stream, - object[] evt, - ExpectedStreamVersion version, - CancellationToken cancellationToken - ) { - var streamEvents = evt.Select(x => new NewStreamEvent(Guid.NewGuid(), x, new())); - - return fixture.EventWriter.AppendEvents(stream, version, streamEvents.ToArray(), cancellationToken); - } + extension(IntegrationFixture fixture) { + public Task AppendEvents( + StreamName stream, + object[] evt, + ExpectedStreamVersion version, + CancellationToken cancellationToken + ) { + var streamEvents = evt.Select(x => new NewStreamEvent(Guid.NewGuid(), x, new())); + + return fixture.EventWriter.AppendEvents(stream, version, streamEvents.ToArray(), cancellationToken); + } - public static Task AppendEvent(this IntegrationFixture fixture, StreamName stream, object evt, ExpectedStreamVersion version, CancellationToken cancellationToken) { - var streamEvent = new NewStreamEvent(Guid.NewGuid(), evt, new()); + public Task AppendEvent(StreamName stream, object evt, ExpectedStreamVersion version, CancellationToken cancellationToken) { + var streamEvent = new NewStreamEvent(Guid.NewGuid(), evt, new()); - return fixture.EventWriter.AppendEvents(stream, version, [streamEvent], cancellationToken); + return fixture.EventWriter.AppendEvents(stream, version, [streamEvent], cancellationToken); + } } } diff --git a/src/SqlServer/src/Eventuous.SqlServer/Extensions/RegistrationExtensions.cs b/src/SqlServer/src/Eventuous.SqlServer/Extensions/RegistrationExtensions.cs index e7b8461c..f65f0420 100644 --- a/src/SqlServer/src/Eventuous.SqlServer/Extensions/RegistrationExtensions.cs +++ b/src/SqlServer/src/Eventuous.SqlServer/Extensions/RegistrationExtensions.cs @@ -14,76 +14,75 @@ namespace Microsoft.Extensions.DependencyInjection; public static class ServiceCollectionExtensions { - /// - /// Adds SQL Server event store and the necessary schema to the DI container. - /// /// Service collection - /// Connection string - /// Schema name - /// Set to true if you want the schema to be created on startup - /// - public static IServiceCollection AddEventuousSqlServer( - this IServiceCollection services, - string connectionString, - string schema = Schema.DefaultSchema, - bool initializeDatabase = false - ) { - var options = new SqlServerStoreOptions { - Schema = Ensure.NotEmptyString(schema), - ConnectionString = Ensure.NotEmptyString(connectionString), - InitializeDatabase = initializeDatabase - }; - services.AddSingleton(options); - services.AddSingleton(); - services.AddHostedService(); - services.TryAddSingleton(new SqlServerConnectionOptions(connectionString, schema)); + extension(IServiceCollection services) { + /// + /// Adds SQL Server event store and the necessary schema to the DI container. + /// + /// Connection string + /// Schema name + /// Set to true if you want the schema to be created on startup + /// + public IServiceCollection AddEventuousSqlServer( + string connectionString, + string schema = Schema.DefaultSchema, + bool initializeDatabase = false + ) { + var options = new SqlServerStoreOptions { + Schema = Ensure.NotEmptyString(schema), + ConnectionString = Ensure.NotEmptyString(connectionString), + InitializeDatabase = initializeDatabase + }; + services.AddSingleton(options); + services.AddSingleton(); + services.AddHostedService(); + services.TryAddSingleton(new SqlServerConnectionOptions(connectionString, schema)); - return services; - } + return services; + } - /// - /// Adds SQL Server event store and the necessary schema to the DI container using the configuration. - /// - /// Services collection - /// Configuration section for SQL Server options - /// - public static IServiceCollection AddEventuousSqlServer(this IServiceCollection services, IConfiguration config) { - services.Configure(config); - services.AddSingleton(sp => sp.GetRequiredService>().Value); - services.AddSingleton(); - services.AddHostedService(); + /// + /// Adds SQL Server event store and the necessary schema to the DI container using the configuration. + /// + /// Configuration section for SQL Server options + /// + public IServiceCollection AddEventuousSqlServer(IConfiguration config) { + services.Configure(config); + services.AddSingleton(sp => sp.GetRequiredService>().Value); + services.AddSingleton(); + services.AddHostedService(); - services.TryAddSingleton( - sp => { - var storeOptions = sp.GetRequiredService>().Value; + services.TryAddSingleton( + sp => { + var storeOptions = sp.GetRequiredService>().Value; - return new SqlServerConnectionOptions(Ensure.NotEmptyString(storeOptions.ConnectionString), storeOptions.Schema); - } - ); + return new SqlServerConnectionOptions(Ensure.NotEmptyString(storeOptions.ConnectionString), storeOptions.Schema); + } + ); - return services; - } + return services; + } - /// - /// Registers the SQL Server-based checkpoint store using the details provided when registering - /// SQL Server connection factory. - /// - /// Services collection - /// - public static IServiceCollection AddSqlServerCheckpointStore(this IServiceCollection services) - => services.AddCheckpointStore( - sp => { - var loggerFactory = sp.GetService(); - var connectionOptions = sp.GetService(); - var checkpointStoreOptions = sp.GetService(); + /// + /// Registers the SQL Server-based checkpoint store using the details provided when registering + /// SQL Server connection factory. + /// + /// + public IServiceCollection AddSqlServerCheckpointStore() + => services.AddCheckpointStore( + sp => { + var loggerFactory = sp.GetService(); + var connectionOptions = sp.GetService(); + var checkpointStoreOptions = sp.GetService(); - var schema = connectionOptions?.Schema is not null and not Schema.DefaultSchema - && checkpointStoreOptions?.Schema is null or Schema.DefaultSchema - ? connectionOptions.Schema - : checkpointStoreOptions?.Schema ?? Schema.DefaultSchema; - var connectionString = checkpointStoreOptions?.ConnectionString ?? connectionOptions?.ConnectionString; + var schema = connectionOptions?.Schema is not null and not Schema.DefaultSchema + && checkpointStoreOptions?.Schema is null or Schema.DefaultSchema + ? connectionOptions.Schema + : checkpointStoreOptions?.Schema ?? Schema.DefaultSchema; + var connectionString = checkpointStoreOptions?.ConnectionString ?? connectionOptions?.ConnectionString; - return new(Ensure.NotNull(connectionString), schema, loggerFactory); - } - ); + return new(Ensure.NotNull(connectionString), schema, loggerFactory); + } + ); + } } diff --git a/src/SqlServer/src/Eventuous.SqlServer/Extensions/SqlExtensions.cs b/src/SqlServer/src/Eventuous.SqlServer/Extensions/SqlExtensions.cs index cd853188..1f0bea95 100644 --- a/src/SqlServer/src/Eventuous.SqlServer/Extensions/SqlExtensions.cs +++ b/src/SqlServer/src/Eventuous.SqlServer/Extensions/SqlExtensions.cs @@ -6,56 +6,60 @@ namespace Eventuous.SqlServer.Extensions; static class SqlExtensions { - internal static SqlCommand AddPersistedEvent(this SqlCommand command, string parameterName, IEnumerable persistedEvents) { - var tableVariable = new DataTable(); - - tableVariable.Columns.Add("message_id", typeof(Guid)); - tableVariable.Columns.Add("message_type", typeof(string)); - tableVariable.Columns.Add("json_data", typeof(string)); - tableVariable.Columns.Add("json_metadata", typeof(string)); - - foreach (var persistedEvent in persistedEvents) { - var row = tableVariable.NewRow(); - row["message_id"] = persistedEvent.MessageId; - row["message_type"] = persistedEvent.MessageType; - row["json_data"] = persistedEvent.JsonData; - row["json_metadata"] = persistedEvent.JsonMetadata; - tableVariable.Rows.Add(row); - } + extension(SqlCommand command) { + internal SqlCommand AddPersistedEvent(string parameterName, IEnumerable persistedEvents) { + var tableVariable = new DataTable(); - return command.Add(parameterName, SqlDbType.Structured, tableVariable); - } + tableVariable.Columns.Add("message_id", typeof(Guid)); + tableVariable.Columns.Add("message_type", typeof(string)); + tableVariable.Columns.Add("json_data", typeof(string)); + tableVariable.Columns.Add("json_metadata", typeof(string)); - internal static SqlParameter AddOutputParameter(this SqlCommand command, string parameterName, SqlDbType sqlDbType) - => command.Parameters.Add(new() { ParameterName = parameterName, SqlDbType = sqlDbType, Direction = ParameterDirection.Output }); + foreach (var persistedEvent in persistedEvents) { + var row = tableVariable.NewRow(); + row["message_id"] = persistedEvent.MessageId; + row["message_type"] = persistedEvent.MessageType; + row["json_data"] = persistedEvent.JsonData; + row["json_metadata"] = persistedEvent.JsonMetadata; + tableVariable.Rows.Add(row); + } - internal static SqlCommand AddOutput(this SqlCommand command, string parameterName, SqlDbType sqlDbType) { - command.Parameters.Add(new() { ParameterName = parameterName, SqlDbType = sqlDbType, Direction = ParameterDirection.Output }); + return command.Add(parameterName, SqlDbType.Structured, tableVariable); + } - return command; - } + internal SqlParameter AddOutputParameter(string parameterName, SqlDbType sqlDbType) + => command.Parameters.Add(new() { ParameterName = parameterName, SqlDbType = sqlDbType, Direction = ParameterDirection.Output }); - internal static SqlCommand Add(this SqlCommand command, string parameterName, SqlDbType sqlDbType, object value) { - var param = command.Parameters.AddWithValue(parameterName, value); - param.SqlDbType = sqlDbType; + internal SqlCommand AddOutput(string parameterName, SqlDbType sqlDbType) { + command.Parameters.Add(new() { ParameterName = parameterName, SqlDbType = sqlDbType, Direction = ParameterDirection.Output }); - return command; - } + return command; + } - internal static SqlCommand GetTextCommand(this SqlConnection connection, string sql) { - var cmd = connection.CreateCommand(); - cmd.CommandType = CommandType.Text; - cmd.CommandText = sql; + internal SqlCommand Add(string parameterName, SqlDbType sqlDbType, object value) { + var param = command.Parameters.AddWithValue(parameterName, value); + param.SqlDbType = sqlDbType; - return cmd; + return command; + } } - internal static SqlCommand GetStoredProcCommand(this SqlConnection connection, string storedProcName, SqlTransaction? transaction = null) { - var cmd = connection.CreateCommand(); - cmd.CommandType = CommandType.StoredProcedure; - cmd.CommandText = storedProcName; - if (transaction != null) cmd.Transaction = transaction; + extension(SqlConnection connection) { + internal SqlCommand GetTextCommand(string sql) { + var cmd = connection.CreateCommand(); + cmd.CommandType = CommandType.Text; + cmd.CommandText = sql; + + return cmd; + } - return cmd; + internal SqlCommand GetStoredProcCommand(string storedProcName, SqlTransaction? transaction = null) { + var cmd = connection.CreateCommand(); + cmd.CommandType = CommandType.StoredProcedure; + cmd.CommandText = storedProcName; + if (transaction != null) cmd.Transaction = transaction; + + return cmd; + } } } diff --git a/src/Testing/src/Eventuous.Testing/AggregateFactoryExtensions.cs b/src/Testing/src/Eventuous.Testing/AggregateFactoryExtensions.cs index 5cb09508..7ac5ede5 100644 --- a/src/Testing/src/Eventuous.Testing/AggregateFactoryExtensions.cs +++ b/src/Testing/src/Eventuous.Testing/AggregateFactoryExtensions.cs @@ -4,30 +4,31 @@ namespace Eventuous.Testing; public static class AggregateFactoryExtensions { - /// - /// Creates an instance of the aggregate and assigns the aggregate ID - /// /// Aggregate factory registry - /// Aggregate identity - /// Aggregate type - /// Aggregate state type - /// Aggregate identity type - /// - [Obsolete("This overload is for backwards compability. Use CreateTestAggregateInstance that uses Id in stead of AggregateId as TId parameter.")] - public static TAggregate CreateTestAggregateInstanceForAggregateId(this AggregateFactoryRegistry registry, TId id) - where TAggregate : Aggregate where TState : State, new() where TId : AggregateId - => registry.CreateInstance().WithId(id); + extension(AggregateFactoryRegistry registry) { + /// + /// Creates an instance of the aggregate and assigns the aggregate ID + /// + /// Aggregate identity + /// Aggregate type + /// Aggregate state type + /// Aggregate identity type + /// + [Obsolete("This overload is for backwards compability. Use CreateTestAggregateInstance that uses Id in stead of AggregateId as TId parameter.")] + public TAggregate CreateTestAggregateInstanceForAggregateId(TId id) + where TAggregate : Aggregate where TState : State, new() where TId : AggregateId + => registry.CreateInstance().WithId(id); - /// - /// Creates an instance of the aggregate and assigns the ID of the aggregate - /// - /// Aggregate factory registry - /// Aggregate identity - /// Aggregate type - /// Aggregate state type - /// Aggregate identity type - /// - public static TAggregate CreateTestAggregateInstance(this AggregateFactoryRegistry registry, TId id) - where TAggregate : Aggregate where TState : State, new() where TId : Id - => registry.CreateInstance().WithId(id); + /// + /// Creates an instance of the aggregate and assigns the ID of the aggregate + /// + /// Aggregate identity + /// Aggregate type + /// Aggregate state type + /// Aggregate identity type + /// + public TAggregate CreateTestAggregateInstance(TId id) + where TAggregate : Aggregate where TState : State, new() where TId : Id + => registry.CreateInstance().WithId(id); + } } diff --git a/test/Eventuous.Sut.Domain/BookingState.cs b/test/Eventuous.Sut.Domain/BookingState.cs index 13cef1a5..999a9740 100644 --- a/test/Eventuous.Sut.Domain/BookingState.cs +++ b/test/Eventuous.Sut.Domain/BookingState.cs @@ -12,7 +12,7 @@ public BookingState() { On( (state, paid) => state with { AmountPaid = state.AmountPaid + new Money(paid.AmountPaid), - _registeredPayments = state._registeredPayments.Add(new(paid.PaymentId, new Money(paid.AmountPaid))) + _registeredPayments = state._registeredPayments.Add(new(paid.PaymentId, new(paid.AmountPaid))) } ); } diff --git a/test/Eventuous.TestHelpers.TUnit/IntToTimespan.cs b/test/Eventuous.TestHelpers.TUnit/IntToTimespan.cs index cc168353..04973ac6 100644 --- a/test/Eventuous.TestHelpers.TUnit/IntToTimespan.cs +++ b/test/Eventuous.TestHelpers.TUnit/IntToTimespan.cs @@ -4,7 +4,8 @@ namespace Eventuous.TestHelpers.TUnit; public static class IntToTimespan { - public static TimeSpan Seconds(this int value) => TimeSpan.FromSeconds(value); - - public static TimeSpan Milliseconds(this int value) => TimeSpan.FromMilliseconds(value); + extension(int value) { + public TimeSpan Seconds() => TimeSpan.FromSeconds(value); + public TimeSpan Milliseconds() => TimeSpan.FromMilliseconds(value); + } } diff --git a/test/Eventuous.TestHelpers.TUnit/Logging/LoggingExtensions.cs b/test/Eventuous.TestHelpers.TUnit/Logging/LoggingExtensions.cs index bc8927a4..717e095b 100644 --- a/test/Eventuous.TestHelpers.TUnit/Logging/LoggingExtensions.cs +++ b/test/Eventuous.TestHelpers.TUnit/Logging/LoggingExtensions.cs @@ -5,12 +5,11 @@ namespace Eventuous.TestHelpers.TUnit.Logging; public static class LoggingExtensions { public static ILoggerFactory GetLoggerFactory(LogLevel logLevel = LogLevel.Information) - => LoggerFactory.Create( - builder => builder - .SetMinimumLevel(logLevel) - .AddFilter("Microsoft", LogLevel.Warning) - .AddFilter("Grpc", LogLevel.Warning) - .AddTUnit(logLevel) + => LoggerFactory.Create(builder => builder + .SetMinimumLevel(logLevel) + .AddFilter("Microsoft", LogLevel.Warning) + .AddFilter("Grpc", LogLevel.Warning) + .AddTUnit(logLevel) ); public static ILoggerFactory AddTUnit(this ILoggerFactory factory, LogLevel logLevel) { @@ -19,15 +18,17 @@ public static ILoggerFactory AddTUnit(this ILoggerFactory factory, LogLevel logL return factory; } - public static ILoggingBuilder AddTUnit(this ILoggingBuilder builder, LogLevel logLevel) => builder.AddProvider(new TUnitLoggerProvider(logLevel)); + extension(ILoggingBuilder builder) { + public ILoggingBuilder AddTUnit(LogLevel logLevel) => builder.AddProvider(new TUnitLoggerProvider(logLevel)); - public static ILoggingBuilder ForTests(this ILoggingBuilder builder, LogLevel logLevel = LogLevel.Information) - => builder - .AddTUnit(logLevel) - .SetMinimumLevel(logLevel) - .AddFilter("Grpc", LogLevel.Warning) - .AddFilter("Microsoft", LogLevel.Warning) - .AddFilter("Npgsql", LogLevel.Warning); + public ILoggingBuilder ForTests(LogLevel logLevel = LogLevel.Information) + => builder + .AddTUnit(logLevel) + .SetMinimumLevel(logLevel) + .AddFilter("Grpc", LogLevel.Warning) + .AddFilter("Microsoft", LogLevel.Warning) + .AddFilter("Npgsql", LogLevel.Warning); + } } public sealed class TUnitLoggerProvider(LogLevel logLevel) : ILoggerProvider {