diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4247e0d66..9dcad744a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -28,12 +28,12 @@ jobs: Hosting.Dapr.Tests, Hosting.DbGate.Tests, Hosting.Deno.Tests, - Hosting.EventStore.Tests, Hosting.Flagd.Tests, Hosting.GoFeatureFlag.Tests, Hosting.Golang.Tests, Hosting.Java.Tests, Hosting.k6.Tests, + Hosting.KurrentDB.Tests, Hosting.LavinMQ.Tests, Hosting.MailPit.Tests, Hosting.McpInspector.Tests, @@ -59,8 +59,8 @@ jobs: Hosting.SurrealDb.Tests, # Client integration tests - EventStore.Tests, GoFeatureFlag.Tests, + KurrentDB.Tests, MassTransit.RabbitMQ.Tests, Meilisearch.Tests, Microsoft.Data.Sqlite.Tests, diff --git a/CommunityToolkit.Aspire.slnx b/CommunityToolkit.Aspire.slnx index ba2cd5434..bce504b54 100644 --- a/CommunityToolkit.Aspire.slnx +++ b/CommunityToolkit.Aspire.slnx @@ -35,6 +35,11 @@ + + + + + @@ -174,6 +179,7 @@ + @@ -197,6 +203,7 @@ + @@ -212,7 +219,6 @@ - @@ -220,12 +226,12 @@ - + @@ -249,6 +255,7 @@ + diff --git a/Directory.Packages.props b/Directory.Packages.props index 98fea59f7..0c8897238 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -18,6 +18,7 @@ + diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/Account.cs b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/Account.cs new file mode 100644 index 000000000..5c814a326 --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/Account.cs @@ -0,0 +1,105 @@ +using System.Text.Json.Serialization; + +namespace CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService; + +public class Account +{ + public Guid Id { get; private set; } + public string? Name { get; private set; } + public decimal Balance { get; private set; } + + [JsonIgnore] + public int Version { get; private set; } = -1; + + [NonSerialized] + private readonly Queue uncommittedEvents = new(); + + public static Account Create(Guid id, string name) + => new(id, name); + + public void Deposit(decimal amount) + { + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(amount, 0, nameof(amount)); + + var @event = new AccountFundsDeposited(Id, amount); + + uncommittedEvents.Enqueue(@event); + Apply(@event); + } + + public void Withdraw(decimal amount) + { + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(amount, 0, nameof(amount)); + ArgumentOutOfRangeException.ThrowIfGreaterThan(amount, Balance, nameof(amount)); + + var @event = new AccountFundsWithdrew(Id, amount); + + uncommittedEvents.Enqueue(@event); + Apply(@event); + } + + public void When(object @event) + { + switch (@event) + { + case AccountCreated accountCreated: + Apply(accountCreated); + break; + case AccountFundsDeposited accountFundsDeposited: + Apply(accountFundsDeposited); + break; + case AccountFundsWithdrew accountFundsWithdrew: + Apply(accountFundsWithdrew); + break; + } + } + + public object[] DequeueUncommittedEvents() + { + var dequeuedEvents = uncommittedEvents.ToArray(); + + uncommittedEvents.Clear(); + + return dequeuedEvents; + } + + private Account() + { + } + + private Account(Guid id, string name) + { + if (id == Guid.Empty) + { + throw new ArgumentException("Id cannot be empty.", nameof(id)); + } + ArgumentException.ThrowIfNullOrWhiteSpace(name, nameof(name)); + + var @event = new AccountCreated(id, name); + + uncommittedEvents.Enqueue(@event); + Apply(@event); + } + + private void Apply(AccountCreated @event) + { + Version++; + + Id = @event.Id; + Name = @event.Name; + } + + private void Apply(AccountFundsDeposited @event) + { + Version++; + + Balance += @event.Amount; + } + + private void Apply(AccountFundsWithdrew @event) + { + Version++; + + Balance -= @event.Amount; + } +} diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/AccountEvents.cs b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/AccountEvents.cs new file mode 100644 index 000000000..5ae87805c --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/AccountEvents.cs @@ -0,0 +1,7 @@ +namespace CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService; + +public record AccountCreated(Guid Id, string Name); + +public record AccountFundsDeposited(Guid Id, decimal Amount); + +public record AccountFundsWithdrew(Guid Id, decimal Amount); diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService.csproj b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService.csproj new file mode 100644 index 000000000..50174618e --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService.csproj @@ -0,0 +1,18 @@ + + + + enable + enable + + + + + + + + + + + + + diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/KurrentDBExtensions.cs b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/KurrentDBExtensions.cs new file mode 100644 index 000000000..71160a21d --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/KurrentDBExtensions.cs @@ -0,0 +1,76 @@ +using System.Text.Json; +using System.Text; +using KurrentDB.Client; + +namespace CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService; + +public static class KurrentDBExtensions +{ + public static async Task GetAccount(this KurrentDBClient eventStore, Guid id, CancellationToken cancellationToken) + { + var readResult = eventStore.ReadStreamAsync( + Direction.Forwards, + $"account-{id:N}", + StreamPosition.Start, + cancellationToken: cancellationToken + ); + + var readState = await readResult.ReadState; + if (readState == ReadState.StreamNotFound) + { + return null; + } + + var account = (Account)Activator.CreateInstance(typeof(Account), true)!; + + await foreach (var resolvedEvent in readResult) + { + var @event = resolvedEvent.Deserialize(); + + account.When(@event!); + } + + return account; + } + + public static async Task AppendAccountEvents(this KurrentDBClient eventStore, Account account, CancellationToken cancellationToken) + { + var events = account.DequeueUncommittedEvents(); + + var eventsToAppend = events + .Select(@event => @event.Serialize()).ToArray(); + + var expectedVersion = account.Version - events.Length; + await eventStore.AppendToStreamAsync( + $"account-{account.Id:N}", + expectedVersion == 0 ? StreamState.NoStream : StreamState.StreamRevision((ulong)expectedVersion), + eventsToAppend, + cancellationToken: cancellationToken + ); + } + + private static object? Deserialize(this ResolvedEvent resolvedEvent) + { + var eventClrTypeName = JsonDocument.Parse(resolvedEvent.Event.Metadata) + .RootElement + .GetProperty("EventClrTypeName") + .GetString(); + + return JsonSerializer.Deserialize( + Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span), + Type.GetType(eventClrTypeName!)!); + } + + private static EventData Serialize(this object @event) + { + return new EventData( + Uuid.NewUuid(), + @event.GetType().Name, + data: Encoding.UTF8.GetBytes(JsonSerializer.Serialize(@event)), + metadata: Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new Dictionary + { + { "EventClrTypeName", @event.GetType().AssemblyQualifiedName! } + })) + ); + } +} diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/Program.cs b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/Program.cs new file mode 100644 index 000000000..0b102a3e5 --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/Program.cs @@ -0,0 +1,81 @@ +using CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService; +using KurrentDB.Client; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +builder.AddKurrentDBClient("kurrentdb"); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); + +app.MapDefaultEndpoints(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.MapPost("/account/create", async (KurrentDBClient eventStore, CancellationToken cancellationToken) => +{ + var account = Account.Create(Guid.NewGuid(), "John Doe"); + + account.Deposit(100); + + await eventStore.AppendAccountEvents(account, cancellationToken); + + return Results.Created($"/account/{account.Id}", account); +}); + +app.MapGet("/account/{id:guid}", async (Guid id, KurrentDBClient eventStore, CancellationToken cancellationToken) => +{ + var account = await eventStore.GetAccount(id, cancellationToken); + if (account is null) + { + return Results.NotFound(); + } + + return TypedResults.Ok(account); +}); + +app.MapPost("/account/{id:guid}/deposit", async (Guid id, DepositRequest request, KurrentDBClient eventStore, CancellationToken cancellationToken) => +{ + var account = await eventStore.GetAccount(id, cancellationToken); + if (account is null) + { + return Results.NotFound(); + } + + account.Deposit(request.Amount); + + await eventStore.AppendAccountEvents(account, cancellationToken); + + return Results.Ok(); +}); + +app.MapPost("/account/{id:guid}/withdraw", async (Guid id, WithdrawRequest request, KurrentDBClient eventStore, CancellationToken cancellationToken) => +{ + var account = await eventStore.GetAccount(id, cancellationToken); + if (account is null) + { + return Results.NotFound(); + } + + account.Withdraw(request.Amount); + + await eventStore.AppendAccountEvents(account, cancellationToken); + + return Results.Ok(); +}); + +app.Run(); + +public record DepositRequest(decimal Amount); +public record WithdrawRequest(decimal Amount); diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/Properties/launchSettings.json b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/Properties/launchSettings.json new file mode 100644 index 000000000..08fc43745 --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:38959", + "sslPort": 44303 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5279", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7015;http://localhost:5279", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/appsettings.json b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ApiService/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost.csproj b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost.csproj new file mode 100644 index 000000000..f11be1abf --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost.csproj @@ -0,0 +1,21 @@ + + + + + Exe + enable + enable + true + 9ea31b5e-317f-4692-8a61-e60ac7ec0d0a + + + + + + + + + + + + diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/Program.cs b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/Program.cs new file mode 100644 index 000000000..67f71f309 --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/Program.cs @@ -0,0 +1,11 @@ +using Projects; + +var builder = DistributedApplication.CreateBuilder(args); + +var kurrentdb = builder.AddKurrentDB("kurrentdb", 22113); + +builder.AddProject("apiservice") + .WithReference(kurrentdb) + .WaitFor(kurrentdb); + +builder.Build().Run(); diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/Properties/launchSettings.json b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/Properties/launchSettings.json new file mode 100644 index 000000000..f996ed794 --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17038;http://localhost:15090", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21125", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22133" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15090", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19068", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20178" + } + } + } +} diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/appsettings.json b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/appsettings.json new file mode 100644 index 000000000..31c092aa4 --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ServiceDefaults/CommunityToolkit.Aspire.Hosting.KurrentDB.ServiceDefaults.csproj b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ServiceDefaults/CommunityToolkit.Aspire.Hosting.KurrentDB.ServiceDefaults.csproj new file mode 100644 index 000000000..caa6344dc --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ServiceDefaults/CommunityToolkit.Aspire.Hosting.KurrentDB.ServiceDefaults.csproj @@ -0,0 +1,21 @@ + + + + enable + enable + true + + + + + + + + + + + + + + + diff --git a/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ServiceDefaults/Extensions.cs b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ServiceDefaults/Extensions.cs new file mode 100644 index 000000000..1081a52f3 --- /dev/null +++ b/examples/kurrentdb/CommunityToolkit.Aspire.Hosting.KurrentDB.ServiceDefaults/Extensions.cs @@ -0,0 +1,117 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation() + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/src/CommunityToolkit.Aspire.EventStore/AspireEventStoreExtensions.cs b/src/CommunityToolkit.Aspire.EventStore/AspireEventStoreExtensions.cs index 29bcef4e0..3e5536949 100644 --- a/src/CommunityToolkit.Aspire.EventStore/AspireEventStoreExtensions.cs +++ b/src/CommunityToolkit.Aspire.EventStore/AspireEventStoreExtensions.cs @@ -15,6 +15,7 @@ namespace Microsoft.Extensions.Hosting; /// /// Provides extension methods for registering EventStore-related services in an . /// +[Obsolete("EventStore has been rebranded to KurrentDB. Use CommunityToolkit.Aspire.KurrentDB and AspireKurrentDBExtensions instead. This integration will be removed in a future release.")] public static class AspireEventStoreExtensions { private const string DefaultConfigSectionName = "Aspire:EventStore:Client"; diff --git a/src/CommunityToolkit.Aspire.EventStore/EventStoreSettings.cs b/src/CommunityToolkit.Aspire.EventStore/EventStoreSettings.cs index 3c341e7e2..2497d6d9d 100644 --- a/src/CommunityToolkit.Aspire.EventStore/EventStoreSettings.cs +++ b/src/CommunityToolkit.Aspire.EventStore/EventStoreSettings.cs @@ -6,6 +6,7 @@ namespace CommunityToolkit.Aspire.EventStore; /// /// Provides the client configuration settings for connecting to an EventStore server using EventStoreClient. /// +[Obsolete("EventStore has been rebranded to KurrentDB. Use CommunityToolkit.Aspire.KurrentDB and KurrentDBSettings instead. This integration will be removed in a future release.")] public sealed class EventStoreSettings { /// diff --git a/src/CommunityToolkit.Aspire.Hosting.EventStore/EventStoreBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.EventStore/EventStoreBuilderExtensions.cs index a19bafd86..92daf80f2 100644 --- a/src/CommunityToolkit.Aspire.Hosting.EventStore/EventStoreBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.EventStore/EventStoreBuilderExtensions.cs @@ -13,6 +13,7 @@ namespace Aspire.Hosting; /// /// Provides extension methods for adding EventStore resources to the application model. /// +[Obsolete("EventStore has been rebranded to KurrentDB. Use CommunityToolkit.Aspire.Hosting.KurrentDB and KurrentDBBuilderExtensions instead. This integration will be removed in a future release.")] public static class EventStoreBuilderExtensions { private const string DataTargetFolder = "/var/lib/eventstore"; diff --git a/src/CommunityToolkit.Aspire.Hosting.EventStore/EventStoreResource.cs b/src/CommunityToolkit.Aspire.Hosting.EventStore/EventStoreResource.cs index a0969d78c..cb7d0a8c0 100644 --- a/src/CommunityToolkit.Aspire.Hosting.EventStore/EventStoreResource.cs +++ b/src/CommunityToolkit.Aspire.Hosting.EventStore/EventStoreResource.cs @@ -7,6 +7,7 @@ namespace Aspire.Hosting.ApplicationModel; /// A resource that represents an EventStore container. /// /// The name of the resource. +[Obsolete("EventStore has been rebranded to KurrentDB. Use CommunityToolkit.Aspire.Hosting.KurrentDB and KurrentDBResource instead. This integration will be removed in a future release.")] public class EventStoreResource(string name) : ContainerResource(name), IResourceWithConnectionString { internal const string HttpEndpointName = "http"; diff --git a/src/CommunityToolkit.Aspire.Hosting.KurrentDB/CommunityToolkit.Aspire.Hosting.KurrentDB.csproj b/src/CommunityToolkit.Aspire.Hosting.KurrentDB/CommunityToolkit.Aspire.Hosting.KurrentDB.csproj new file mode 100644 index 000000000..98b06e700 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.KurrentDB/CommunityToolkit.Aspire.Hosting.KurrentDB.csproj @@ -0,0 +1,17 @@ + + + + hosting kurrentdb + KurrentDB support for .NET Aspire. + + + + + + + + + + + + diff --git a/src/CommunityToolkit.Aspire.Hosting.KurrentDB/KurrentDBBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.KurrentDB/KurrentDBBuilderExtensions.cs new file mode 100644 index 000000000..3e6903bf6 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.KurrentDB/KurrentDBBuilderExtensions.cs @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Utils; +using CommunityToolkit.Aspire.Hosting.KurrentDB; +using HealthChecks.EventStore.gRPC; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace Aspire.Hosting; + +/// +/// Provides extension methods for adding KurrentDB resources to the application model. +/// +public static class KurrentDBBuilderExtensions +{ + private const string DataTargetFolder = "/var/lib/kurrentdb"; + + /// + /// Adds a KurrentDB resource to the application model. A container is used for local development. + /// The default image is and the tag is . + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The port on which the KurrentDB endpoint will be exposed. + /// A reference to the . + /// + /// + /// Add a KurrentDB container to the application model and reference it in a .NET project. + /// + /// var builder = DistributedApplication.CreateBuilder(args); + /// + /// var kurrentdb = builder.AddKurrentDB("kurrentdb"); + /// var api = builder.AddProject<Projects.Api>("api") + /// .WithReference(kurrentdb); + /// + /// builder.Build().Run(); + /// + /// + /// + public static IResourceBuilder AddKurrentDB(this IDistributedApplicationBuilder builder, [ResourceName] string name, int? port = null) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(name); + + var kurrentDBResource = new KurrentDBResource(name); + + string? connectionString = null; + + builder.Eventing.Subscribe(kurrentDBResource, async (@event, cancellationToken) => + { + connectionString = await kurrentDBResource.ConnectionStringExpression + .GetValueAsync(cancellationToken) + .ConfigureAwait(false) + ?? throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{kurrentDBResource.Name}' resource but the connection string was null."); + }); + + var healthCheckKey = $"{name}_check"; + builder.Services.AddHealthChecks() + .Add(new HealthCheckRegistration( + healthCheckKey, + sp => new EventStoreHealthCheck(connectionString!), + failureStatus: default, + tags: default, + timeout: default)); + + return builder + .AddResource(kurrentDBResource) + .WithHttpEndpoint(port: port, targetPort: KurrentDBResource.DefaultHttpPort, name: KurrentDBResource.HttpEndpointName) + .WithImage(KurrentDBContainerImageTags.Image, KurrentDBContainerImageTags.Tag) + .WithImageRegistry(KurrentDBContainerImageTags.Registry) + .WithEnvironment(ConfigureKurrentDBContainer) + .WithHealthCheck(healthCheckKey); + } + + /// + /// Adds a named volume for the data folder to a KurrentDB container resource. + /// + /// The resource builder. + /// The name of the volume. Defaults to an auto-generated name based on the application and resource names. + /// The . + /// + /// + /// Add a KurrentDB container to the application model and reference it in a .NET project. Additionally, in this + /// example a data volume is added to the container to allow data to be persisted across container restarts. + /// + /// var builder = DistributedApplication.CreateBuilder(args); + /// + /// var kurrentdb = builder.AddKurrentDB("kurrentdb") + /// .WithDataVolume(); + /// var api = builder.AddProject<Projects.Api>("api") + /// .WithReference(kurrentdb); + /// + /// builder.Build().Run(); + /// + /// + /// + public static IResourceBuilder WithDataVolume(this IResourceBuilder builder, string? name = null) + { + ArgumentNullException.ThrowIfNull(builder); + + return builder.WithVolume(name ?? VolumeNameGenerator.Generate(builder, "data"), DataTargetFolder); + } + + /// + /// Adds a bind mount for the data folder to a KurrentDB container resource. + /// + /// The resource builder. + /// The source directory on the host to mount into the container. + /// The . + /// + /// + /// Add a KurrentDB container to the application model and reference it in a .NET project. Additionally, in this + /// example a bind mount is added to the container to allow data to be persisted across container restarts. + /// + /// var builder = DistributedApplication.CreateBuilder(args); + /// + /// var kurrentdb = builder.AddKurrentDB("kurrentdb") + /// .WithDataBindMount("./data/kurrentdb/data"); + /// var api = builder.AddProject<Projects.Api>("api") + /// .WithReference(kurrentdb); + /// + /// builder.Build().Run(); + /// + /// + /// + public static IResourceBuilder WithDataBindMount(this IResourceBuilder builder, string source) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(source); + + return builder.WithBindMount(source, DataTargetFolder); + } + + private static void ConfigureKurrentDBContainer(EnvironmentCallbackContext context) + { + context.EnvironmentVariables.Add("EVENTSTORE_CLUSTER_SIZE", "1"); + context.EnvironmentVariables.Add("EVENTSTORE_RUN_PROJECTIONS", "All"); + context.EnvironmentVariables.Add("EVENTSTORE_START_STANDARD_PROJECTIONS", "true"); + context.EnvironmentVariables.Add("EVENTSTORE_NODE_PORT", $"{KurrentDBResource.DefaultHttpPort}"); + context.EnvironmentVariables.Add("EVENTSTORE_INSECURE", "true"); + } +} diff --git a/src/CommunityToolkit.Aspire.Hosting.KurrentDB/KurrentDBContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.KurrentDB/KurrentDBContainerImageTags.cs new file mode 100644 index 000000000..516a62364 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.KurrentDB/KurrentDBContainerImageTags.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CommunityToolkit.Aspire.Hosting.KurrentDB; + +internal static class KurrentDBContainerImageTags +{ + public const string Registry = "docker.kurrent.io"; + public const string Image = "kurrent-latest/kurrentdb"; + public const string Tag = "25.1"; +} diff --git a/src/CommunityToolkit.Aspire.Hosting.KurrentDB/KurrentDBResource.cs b/src/CommunityToolkit.Aspire.Hosting.KurrentDB/KurrentDBResource.cs new file mode 100644 index 000000000..94977c7aa --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.KurrentDB/KurrentDBResource.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a KurrentDB container. +/// +/// The name of the resource. +public class KurrentDBResource(string name) : ContainerResource(name), IResourceWithConnectionString +{ + internal const string HttpEndpointName = "http"; + internal const int DefaultHttpPort = 2113; + + private EndpointReference? _primaryEndpoint; + + /// + /// Gets the primary endpoint for the KurrentDB server. + /// + public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, HttpEndpointName); + + /// + /// Gets the connection string for the KurrentDB server. + /// + public ReferenceExpression ConnectionStringExpression => + ReferenceExpression.Create( + $"esdb://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}?tls=false"); +} diff --git a/src/CommunityToolkit.Aspire.Hosting.KurrentDB/README.md b/src/CommunityToolkit.Aspire.Hosting.KurrentDB/README.md new file mode 100644 index 000000000..0f3fb578b --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.KurrentDB/README.md @@ -0,0 +1,37 @@ +# CommunityToolkit.Aspire.Hosting.KurrentDB library + +Provides extension methods and resource definitions for the .NET Aspire app host to support running [KurrentDB](https://www.kurrent.io) containers. + +## Getting Started + +### Install the package + +In your app host project, install the package using the following command: + +```dotnetcli +dotnet add package CommunityToolkit.Aspire.Hosting.KurrentDB +``` + +### Example usage + +Then, in the _Program.cs_ file of app host, add a KurrentDB resource and consume the connection using the following methods: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var kurrentdb = builder.AddKurrentDB("kurrentdb"); + +var myService = builder.AddProject() + .WithReference(kurrentdb); + +builder.Build().Run(); +``` + +## Additional Information + +https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-kurrentdb + +## Feedback & contributing + +https://github.com/CommunityToolkit/Aspire + diff --git a/src/CommunityToolkit.Aspire.KurrentDB/AspireKurrentDBExtensions.cs b/src/CommunityToolkit.Aspire.KurrentDB/AspireKurrentDBExtensions.cs new file mode 100644 index 000000000..e7d836803 --- /dev/null +++ b/src/CommunityToolkit.Aspire.KurrentDB/AspireKurrentDBExtensions.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire; +using CommunityToolkit.Aspire.KurrentDB; +using EventStore.Client; +using EventStore.Client.Extensions.OpenTelemetry; +using HealthChecks.EventStore.gRPC; +using KurrentDB.Client; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace Microsoft.Extensions.Hosting; + +/// +/// Provides extension methods for registering KurrentDB-related services in an . +/// +public static class AspireKurrentDBExtensions +{ + private const string DefaultConfigSectionName = "Aspire:KurrentDB:Client"; + + /// + /// Registers as a singleton in the services provided by the . + /// + /// The to read config from and add services to. + /// The connection name to use to find a connection string. + /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. + public static void AddKurrentDBClient( + this IHostApplicationBuilder builder, + string connectionName, + Action? configureSettings = null) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrEmpty(connectionName); + AddKurrentDBClient(builder, DefaultConfigSectionName, configureSettings, connectionName, serviceKey: null); + } + + /// + /// Registers as a keyed singleton for the given in the services provided by the . + /// + /// The to read config from and add services to. + /// The connection name to use to find a connection string. + /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. + public static void AddKeyedKurrentDBClient( + this IHostApplicationBuilder builder, + string name, + Action? configureSettings = null) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrEmpty(name); + AddKurrentDBClient(builder, $"{DefaultConfigSectionName}:{name}", configureSettings, connectionName: name, serviceKey: name); + } + + private static void AddKurrentDBClient( + this IHostApplicationBuilder builder, + string configurationSectionName, + Action? configureSettings, + string connectionName, + string? serviceKey) + { + ArgumentNullException.ThrowIfNull(builder); + + var settings = new KurrentDBSettings(); + builder.Configuration.GetSection(configurationSectionName).Bind(settings); + + if (builder.Configuration.GetConnectionString(connectionName) is string connectionString) + { + settings.ConnectionString = connectionString; + } + + configureSettings?.Invoke(settings); + + if (serviceKey is null) + { + builder.Services.AddSingleton(ConfigureKurrentDBClient); + } + else + { + builder.Services.AddKeyedSingleton(serviceKey, (sp, key) => ConfigureKurrentDBClient(sp)); + } + + if (!settings.DisableTracing) + { + builder.Services.AddOpenTelemetry() + .WithTracing(traceBuilder => traceBuilder.AddEventStoreClientInstrumentation()); + } + + if (!settings.DisableHealthChecks) + { + var healthCheckName = serviceKey is null ? "KurrentDB.Client" : $"KurrentDB.Client_{connectionName}"; + + builder.TryAddHealthCheck(new HealthCheckRegistration( + healthCheckName, + sp => new EventStoreHealthCheck(settings.ConnectionString!), + failureStatus: default, + tags: default, + timeout: settings.HealthCheckTimeout)); + } + + KurrentDBClient ConfigureKurrentDBClient(IServiceProvider serviceProvider) + { + if (settings.ConnectionString is not null) + { + var clientSettings = KurrentDBClientSettings.Create(settings.ConnectionString!); + return new KurrentDBClient(clientSettings); + } + else + { + throw new InvalidOperationException( + $"A KurrentDB client could not be configured. Ensure valid connection information was provided in 'ConnectionStrings:{connectionName}' or either " + + $"{nameof(settings.ConnectionString)} must be provided " + + $"in the '{configurationSectionName}' configuration section."); + } + } + } +} diff --git a/src/CommunityToolkit.Aspire.KurrentDB/CommunityToolkit.Aspire.KurrentDB.csproj b/src/CommunityToolkit.Aspire.KurrentDB/CommunityToolkit.Aspire.KurrentDB.csproj new file mode 100644 index 000000000..c02b1d924 --- /dev/null +++ b/src/CommunityToolkit.Aspire.KurrentDB/CommunityToolkit.Aspire.KurrentDB.csproj @@ -0,0 +1,22 @@ + + + + KurrentDB client + A KurrentDB client that integrates with Aspire, including health checks, logging, and telemetry. + + + + + + + + + + + + + + + + + diff --git a/src/CommunityToolkit.Aspire.KurrentDB/KurrentDBSettings.cs b/src/CommunityToolkit.Aspire.KurrentDB/KurrentDBSettings.cs new file mode 100644 index 000000000..3c78b0e4e --- /dev/null +++ b/src/CommunityToolkit.Aspire.KurrentDB/KurrentDBSettings.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using KurrentDB.Client; + +namespace CommunityToolkit.Aspire.KurrentDB; + +/// +/// Provides the client configuration settings for connecting to a KurrentDB server using . +/// +public sealed class KurrentDBSettings +{ + /// + /// Gets or sets the connection string. + /// + public string? ConnectionString { get; set; } + + /// + /// Gets or sets a boolean value that indicates whether the KurrentDB health check is disabled or not. + /// + /// + /// The default value is . + /// + public bool DisableHealthChecks { get; set; } + + /// + /// Gets or sets the timeout duration for the health check. + /// + /// + /// The default value is . + /// + public TimeSpan? HealthCheckTimeout { get; set; } + + /// + /// Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is disabled or not. + /// + /// + /// The default value is . + /// + public bool DisableTracing { get; set; } +} diff --git a/src/CommunityToolkit.Aspire.KurrentDB/README.md b/src/CommunityToolkit.Aspire.KurrentDB/README.md new file mode 100644 index 000000000..076512110 --- /dev/null +++ b/src/CommunityToolkit.Aspire.KurrentDB/README.md @@ -0,0 +1,115 @@ +# CommunityToolkit.Aspire.KurrentDB + +Registers an [KurrentDBClient](https://github.com/kurrent-io/KurrentDB-Client-Dotnet) in the DI container for connecting to KurrentDB. + +## Getting started + +### Prerequisites + +- KurrentDB cluster. + +### Install the package + +Install the .NET Aspire KurrentDB Client library with [NuGet](https://www.nuget.org): + +```dotnetcli +dotnet add package CommunityToolkit.Aspire.KurrentDB +``` + +## Usage example + +In the _Program.cs_ file of your project, call the `AddKurrentDBClient` extension method to register an `KurrentDBClient` for use via the dependency injection container. The method takes a connection name parameter. + +```csharp +builder.AddKurrentDBClient("kurrentdb"); +``` + +## Configuration + +The .NET Aspire KurrentDB Client integration provides multiple options to configure the server connection based on the requirements and conventions of your project. + +### Use a connection string + +When using a connection string from the `ConnectionStrings` configuration section, you can provide the name of the connection string when calling `builder.AddKurrentDBClient()`: + +```csharp +builder.AddKurrentDBClient("kurrentdb"); +``` + +And then the connection string will be retrieved from the `ConnectionStrings` configuration section: + +```json +{ + "ConnectionStrings": { + "kurrentdb": "esdb://localhost:22113?tls=false" + } +} +``` + +### Use configuration providers + +The .NET Aspire KurrentDB Client integration supports [Microsoft.Extensions.Configuration](https://learn.microsoft.com/dotnet/api/microsoft.extensions.configuration). It loads the `KurrentDBSettings` from configuration by using the `Aspire:KurrentDB:Client` key. Example `appsettings.json` that configures some of the options: + +```json +{ + "Aspire": { + "KurrentDB": { + "Client": { + "ConnectionString": "esdb://localhost:22113?tls=false", + "DisableHealthChecks": true + } + } + } +} +``` + +### Use inline delegates + +Also you can pass the `Action configureSettings` delegate to set up some or all the options inline, for example to set the API key from code: + +```csharp +builder.AddKurrentDBClient("kurrentdb", settings => settings.DisableHealthChecks = true); +``` + +## AppHost extensions + +In your AppHost project, install the `CommunityToolkit.Aspire.Hosting.KurrentDB` library with [NuGet](https://www.nuget.org): + +```dotnetcli +dotnet add package CommunityToolkit.Aspire.Hosting.KurrentDB +``` + +Then, in the _Program.cs_ file of `AppHost`, register KurrentDB and consume the connection using the following methods: + +```csharp +var kurrentdb = builder.AddKurrentDB("kurrentdb"); + +var myService = builder.AddProject() + .WithReference(kurrentdb); +``` + +The `WithReference` method configures a connection in the `MyService` project named `kurrentdb`. In the _Program.cs_ file of `MyService`, the KurrentDB connection can be consumed using: + +```csharp +builder.AddKurrentDBClient("kurrentdb"); +``` + +Then, in your service, inject `KurrentDBClient` and use it to interact with the KurrentDB API: + +```csharp +public class MyService(KurrentDBClient client) +{ + // ... +} +``` + +## Additional documentation + +- https://github.com/kurrent-io/KurrentDB-Client-Dotnet +- https://www.kurrent.io +- https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-kurrentdb + +## Feedback & contributing + +https://github.com/CommunityToolkit/Aspire + diff --git a/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/AppHostTests.cs deleted file mode 100644 index cf24e1669..000000000 --- a/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/AppHostTests.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Components.Common.Tests; -using CommunityToolkit.Aspire.Testing; -using Projects; -using System.Net.Http.Json; - -namespace CommunityToolkit.Aspire.Hosting.EventStore.Tests; - -[RequiresDocker] -public class AppHostTests(AspireIntegrationTestFixture fixture) : IClassFixture> -{ - [Fact] - public async Task ResourceStartsAndRespondsOk() - { - var resourceName = "eventstore"; - await fixture.ResourceNotificationService - .WaitForResourceHealthyAsync(resourceName) - .WaitAsync(TimeSpan.FromMinutes(1)); - - var httpClient = fixture.CreateHttpClient(resourceName); - - var response = await httpClient.GetAsync("/"); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task ApiServiceCreateAccount() - { - var resourceName = "apiservice"; - await fixture.ResourceNotificationService - .WaitForResourceHealthyAsync("eventstore") - .WaitAsync(TimeSpan.FromMinutes(1)); - await fixture.ResourceNotificationService - .WaitForResourceHealthyAsync(resourceName) - .WaitAsync(TimeSpan.FromMinutes(1)); - - var httpClient = fixture.CreateHttpClient(resourceName); - - var createResponse = await httpClient.PostAsJsonAsync("/account/create", new { }); - Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode); - - var location = createResponse.Headers.Location; - - var getResponse = await httpClient.GetAsync(location); - Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode); - - var account = await getResponse.Content.ReadFromJsonAsync(); - Assert.NotNull(account); - Assert.Equal("John Doe", account.Name); - Assert.Equal(100, account.Balance); - } - - [Fact] - public async Task ApiServiceCreateAccountAndDeposit() - { - var resourceName = "apiservice"; - await fixture.ResourceNotificationService - .WaitForResourceHealthyAsync(resourceName) - .WaitAsync(TimeSpan.FromMinutes(1)); - - var httpClient = fixture.CreateHttpClient(resourceName); - - var createResponse = await httpClient.PostAsJsonAsync("/account/create", new { }); - Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode); - - var location = createResponse.Headers.Location; - - var depositResponse = await httpClient.PostAsJsonAsync($"{location!}/deposit", new { Amount = 50 }); - Assert.Equal(HttpStatusCode.OK, depositResponse.StatusCode); - - var getResponse = await httpClient.GetAsync(location); - Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode); - - var account = await getResponse.Content.ReadFromJsonAsync(); - Assert.NotNull(account); - Assert.Equal("John Doe", account.Name); - Assert.Equal(150, account.Balance); - } - - [Fact] - public async Task ApiServiceCreateAccountAndWithdraw() - { - var resourceName = "apiservice"; - await fixture.ResourceNotificationService - .WaitForResourceHealthyAsync(resourceName) - .WaitAsync(TimeSpan.FromMinutes(1)); - - var httpClient = fixture.CreateHttpClient(resourceName); - - var createResponse = await httpClient.PostAsJsonAsync("/account/create", new { }); - Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode); - - var location = createResponse.Headers.Location; - - var depositResponse = await httpClient.PostAsJsonAsync($"{location!}/withdraw", new { Amount = 90 }); - Assert.Equal(HttpStatusCode.OK, depositResponse.StatusCode); - - var getResponse = await httpClient.GetAsync(location); - Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode); - - var account = await getResponse.Content.ReadFromJsonAsync(); - Assert.NotNull(account); - Assert.Equal("John Doe", account.Name); - Assert.Equal(10, account.Balance); - } - - public record AccountDto(Guid Id, string Name, decimal Balance); -} diff --git a/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests.csproj b/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests.csproj deleted file mode 100644 index 60df07d69..000000000 --- a/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/AddEventStoreTests.cs b/tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/AddKurrentDBTests.cs similarity index 67% rename from tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/AddEventStoreTests.cs rename to tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/AddKurrentDBTests.cs index 1707a2fcd..467698783 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/AddEventStoreTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/AddKurrentDBTests.cs @@ -4,29 +4,29 @@ using Aspire.Hosting; using System.Net.Sockets; -namespace CommunityToolkit.Aspire.Hosting.EventStore.Tests; +namespace CommunityToolkit.Aspire.Hosting.KurrentDB.Tests; -public class AddEventStoreTests +public class AddKurrentDBTests { [Fact] - public async Task AddEventStoreContainerWithDefaultsAddsAnnotationMetadata() + public async Task AddKurrentDBContainerWithDefaultsAddsAnnotationMetadata() { var appBuilder = DistributedApplication.CreateBuilder(); - var eventstore = appBuilder.AddEventStore("eventstore"); + var kurrentdb = appBuilder.AddKurrentDB("kurrentdb"); using var app = appBuilder.Build(); var appModel = app.Services.GetRequiredService(); - var containerResource = Assert.Single(appModel.Resources.OfType()); - Assert.Equal("eventstore", containerResource.Name); + var containerResource = Assert.Single(appModel.Resources.OfType()); + Assert.Equal("kurrentdb", containerResource.Name); var endpoints = containerResource.Annotations.OfType(); Assert.Single(endpoints); var primaryEndpoint = Assert.Single(endpoints, e => e.Name == "http"); - Assert.Equal(EventStoreResource.DefaultHttpPort, primaryEndpoint.TargetPort); + Assert.Equal(KurrentDBResource.DefaultHttpPort, primaryEndpoint.TargetPort); Assert.False(primaryEndpoint.IsExternal); Assert.Equal("http", primaryEndpoint.Name); Assert.Null(primaryEndpoint.Port); @@ -35,11 +35,11 @@ public async Task AddEventStoreContainerWithDefaultsAddsAnnotationMetadata() Assert.Equal("http", primaryEndpoint.UriScheme); var containerAnnotation = Assert.Single(containerResource.Annotations.OfType()); - Assert.Equal(EventStoreContainerImageTags.Tag, containerAnnotation.Tag); - Assert.Equal(EventStoreContainerImageTags.Image, containerAnnotation.Image); - Assert.Equal(EventStoreContainerImageTags.Registry, containerAnnotation.Registry); + Assert.Equal(KurrentDBContainerImageTags.Tag, containerAnnotation.Tag); + Assert.Equal(KurrentDBContainerImageTags.Image, containerAnnotation.Image); + Assert.Equal(KurrentDBContainerImageTags.Registry, containerAnnotation.Registry); - var config = await eventstore.Resource.GetEnvironmentVariableValuesAsync(); + var config = await kurrentdb.Resource.GetEnvironmentVariableValuesAsync(); Assert.Collection(config, env => @@ -60,7 +60,7 @@ public async Task AddEventStoreContainerWithDefaultsAddsAnnotationMetadata() env => { Assert.Equal("EVENTSTORE_NODE_PORT", env.Key); - Assert.Equal($"{EventStoreResource.DefaultHttpPort}", env.Value); + Assert.Equal($"{KurrentDBResource.DefaultHttpPort}", env.Value); }, ext => { @@ -70,21 +70,21 @@ public async Task AddEventStoreContainerWithDefaultsAddsAnnotationMetadata() } [Fact] - public async Task EventStoreCreatesConnectionString() + public async Task KurrentDBCreatesConnectionString() { var appBuilder = DistributedApplication.CreateBuilder(); - var eventstore = appBuilder - .AddEventStore("eventstore") + var kurrentdb = appBuilder + .AddKurrentDB("kurrentdb") .WithEndpoint("http", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 22113)); using var app = appBuilder.Build(); var appModel = app.Services.GetRequiredService(); - var connectionStringResource = Assert.Single(appModel.Resources.OfType()) as IResourceWithConnectionString; + var connectionStringResource = Assert.Single(appModel.Resources.OfType()) as IResourceWithConnectionString; var connectionString = await connectionStringResource.GetConnectionStringAsync(); Assert.Equal("esdb://localhost:22113?tls=false", connectionString); - Assert.Equal("esdb://{eventstore.bindings.http.host}:{eventstore.bindings.http.port}?tls=false", connectionStringResource.ConnectionStringExpression.ValueExpression); + Assert.Equal("esdb://{kurrentdb.bindings.http.host}:{kurrentdb.bindings.http.port}?tls=false", connectionStringResource.ConnectionStringExpression.ValueExpression); } } diff --git a/tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests.csproj b/tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests.csproj new file mode 100644 index 000000000..c484cb2da --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests.csproj @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/EventStoreFunctionalTests.cs b/tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/KurrentDBFunctionalTests.cs similarity index 70% rename from tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/EventStoreFunctionalTests.cs rename to tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/KurrentDBFunctionalTests.cs index f8a1bbea9..3b1b431f8 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/EventStoreFunctionalTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/KurrentDBFunctionalTests.cs @@ -1,27 +1,27 @@ -using Aspire.Components.Common.Tests; +using Aspire.Components.Common.Tests; using Aspire.Hosting; using Aspire.Hosting.Utils; -using EventStore.Client; +using KurrentDB.Client; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; using System.Text; using System.Text.Json; using Xunit.Abstractions; -namespace CommunityToolkit.Aspire.Hosting.EventStore.Tests; +namespace CommunityToolkit.Aspire.Hosting.KurrentDB.Tests; [RequiresDocker] -public class EventStoreFunctionalTests(ITestOutputHelper testOutputHelper) +public class KurrentDBFunctionalTests(ITestOutputHelper testOutputHelper) { public const string TestStreamNamePrefix = "account-"; public const string TestAccountName = "John Doe"; [Fact] - public async Task VerifyEventStoreResource() + public async Task VerifyKurrentDBResource() { using var builder = TestDistributedApplicationBuilder.Create().WithTestAndResourceLogging(testOutputHelper); - var eventstore = builder.AddEventStore("eventstore"); + var kurrentdb = builder.AddKurrentDB("kurrentdb"); using var app = builder.Build(); @@ -29,22 +29,22 @@ public async Task VerifyEventStoreResource() var rns = app.Services.GetRequiredService(); - await rns.WaitForResourceHealthyAsync(eventstore.Resource.Name, default); + await rns.WaitForResourceHealthyAsync(kurrentdb.Resource.Name, default); var hostBuilder = Host.CreateApplicationBuilder(); - hostBuilder.Configuration[$"ConnectionStrings:{eventstore.Resource.Name}"] = await eventstore.Resource.ConnectionStringExpression.GetValueAsync(default); + hostBuilder.Configuration[$"ConnectionStrings:{kurrentdb.Resource.Name}"] = await kurrentdb.Resource.ConnectionStringExpression.GetValueAsync(default); - hostBuilder.AddEventStoreClient(eventstore.Resource.Name); + hostBuilder.AddKurrentDBClient(kurrentdb.Resource.Name); using var host = hostBuilder.Build(); await host.StartAsync(); - var eventStoreClient = host.Services.GetRequiredService(); + var kurrentDBClient = host.Services.GetRequiredService(); - var id = await CreateTestDataAsync(eventStoreClient); - await VerifyTestDataAsync(eventStoreClient, id); + var id = await CreateTestDataAsync(kurrentDBClient); + await VerifyTestDataAsync(kurrentDBClient, id); } [Theory] @@ -59,16 +59,16 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) try { using var builder1 = TestDistributedApplicationBuilder.Create().WithTestAndResourceLogging(testOutputHelper); - var eventstore1 = builder1.AddEventStore("eventstore"); + var kurrentdb1 = builder1.AddKurrentDB("kurrentdb"); if (useVolume) { // Use a deterministic volume name to prevent them from exhausting the machines if deletion fails - volumeName = VolumeNameGenerator.Generate(eventstore1, nameof(WithDataShouldPersistStateBetweenUsages)); + volumeName = VolumeNameGenerator.Generate(kurrentdb1, nameof(WithDataShouldPersistStateBetweenUsages)); // if the volume already exists (because of a crashing previous run), delete it DockerUtils.AttemptDeleteDockerVolume(volumeName, throwOnFailure: true); - eventstore1.WithDataVolume(volumeName); + kurrentdb1.WithDataVolume(volumeName); } else { @@ -85,7 +85,7 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) File.SetUnixFileMode(bindMountPath, OwnershipPermissions); } - eventstore1.WithDataBindMount(bindMountPath); + kurrentdb1.WithDataBindMount(bindMountPath); } using (var app = builder1.Build()) @@ -94,23 +94,23 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) var rns = app.Services.GetRequiredService(); - await rns.WaitForResourceHealthyAsync(eventstore1.Resource.Name, default); + await rns.WaitForResourceHealthyAsync(kurrentdb1.Resource.Name, default); try { var hostBuilder = Host.CreateApplicationBuilder(); - hostBuilder.Configuration[$"ConnectionStrings:{eventstore1.Resource.Name}"] = await eventstore1.Resource.ConnectionStringExpression.GetValueAsync(default); + hostBuilder.Configuration[$"ConnectionStrings:{kurrentdb1.Resource.Name}"] = await kurrentdb1.Resource.ConnectionStringExpression.GetValueAsync(default); - hostBuilder.AddEventStoreClient(eventstore1.Resource.Name); + hostBuilder.AddKurrentDBClient(kurrentdb1.Resource.Name); using (var host = hostBuilder.Build()) { await host.StartAsync(); - var eventStoreClient = host.Services.GetRequiredService(); - id = await CreateTestDataAsync(eventStoreClient); - await VerifyTestDataAsync(eventStoreClient, id.Value); + var kurrentDBClient = host.Services.GetRequiredService(); + id = await CreateTestDataAsync(kurrentDBClient); + await VerifyTestDataAsync(kurrentDBClient, id.Value); } } finally @@ -121,15 +121,15 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) } using var builder2 = TestDistributedApplicationBuilder.Create().WithTestAndResourceLogging(testOutputHelper); - var eventstore2 = builder2.AddEventStore("eventstore"); + var kurrentdb2 = builder2.AddKurrentDB("kurrentdb"); if (useVolume) { - eventstore2.WithDataVolume(volumeName); + kurrentdb2.WithDataVolume(volumeName); } else { - eventstore2.WithDataBindMount(bindMountPath!); + kurrentdb2.WithDataBindMount(bindMountPath!); } using (var app = builder2.Build()) @@ -138,22 +138,22 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) var rns = app.Services.GetRequiredService(); - await rns.WaitForResourceHealthyAsync(eventstore1.Resource.Name, default); + await rns.WaitForResourceHealthyAsync(kurrentdb1.Resource.Name, default); try { var hostBuilder = Host.CreateApplicationBuilder(); - hostBuilder.Configuration[$"ConnectionStrings:{eventstore2.Resource.Name}"] = await eventstore2.Resource.ConnectionStringExpression.GetValueAsync(default); + hostBuilder.Configuration[$"ConnectionStrings:{kurrentdb2.Resource.Name}"] = await kurrentdb2.Resource.ConnectionStringExpression.GetValueAsync(default); - hostBuilder.AddEventStoreClient(eventstore2.Resource.Name); + hostBuilder.AddKurrentDBClient(kurrentdb2.Resource.Name); using (var host = hostBuilder.Build()) { await host.StartAsync(); - var eventStoreClient = host.Services.GetRequiredService(); + var kurrentDBClient = host.Services.GetRequiredService(); - await VerifyTestDataAsync(eventStoreClient, id.Value); + await VerifyTestDataAsync(kurrentDBClient, id.Value); } } finally @@ -186,7 +186,7 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume) } [Fact] - public async Task VerifyWaitForEventStoreBlocksDependentResources() + public async Task VerifyWaitForKurrentDBBlocksDependentResources() { var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); using var builder = TestDistributedApplicationBuilder.Create().WithTestAndResourceLogging(testOutputHelper); @@ -197,7 +197,7 @@ public async Task VerifyWaitForEventStoreBlocksDependentResources() return healthCheckTcs.Task; }); - var resource = builder.AddEventStore("resource") + var resource = builder.AddKurrentDB("resource") .WithHealthCheck("blocking_check"); var dependentResource = builder.AddContainer("nginx", "mcr.microsoft.com/cbl-mariner/base/nginx", "1.22") @@ -224,7 +224,7 @@ public async Task VerifyWaitForEventStoreBlocksDependentResources() await app.StopAsync(); } - private static async Task CreateTestDataAsync(EventStoreClient eventStoreClient) + private static async Task CreateTestDataAsync(KurrentDBClient kurrentDBClient) { var id = Guid.NewGuid(); var accountCreated = new AccountCreated(id, TestAccountName); @@ -232,17 +232,17 @@ private static async Task CreateTestDataAsync(EventStoreClient eventStoreC var eventData = new EventData(Uuid.NewUuid(), nameof(AccountCreated), data); var streamName = $"{TestStreamNamePrefix}{id}"; - var writeResult = await eventStoreClient.AppendToStreamAsync(streamName, StreamRevision.None, [eventData]); + var writeResult = await kurrentDBClient.AppendToStreamAsync(streamName, StreamState.NoStream, [eventData]); Assert.NotNull(writeResult); return id; } - private static async Task VerifyTestDataAsync(EventStoreClient eventStoreClient, Guid id) + private static async Task VerifyTestDataAsync(KurrentDBClient kurrentDBClient, Guid id) { var streamName = $"{TestStreamNamePrefix}{id}"; - var readResult = eventStoreClient.ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start); + var readResult = kurrentDBClient.ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start); Assert.NotNull(readResult); var readState = await readResult.ReadState; diff --git a/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/EventStorePublicApiTests.cs b/tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/KurrentDBPublicApiTests.cs similarity index 66% rename from tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/EventStorePublicApiTests.cs rename to tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/KurrentDBPublicApiTests.cs index 4546d29dc..f37716a7c 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.EventStore.Tests/EventStorePublicApiTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/KurrentDBPublicApiTests.cs @@ -1,31 +1,31 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting; -namespace CommunityToolkit.Aspire.Hosting.EventStore.Tests; +namespace CommunityToolkit.Aspire.Hosting.KurrentDB.Tests; -public class EventStorePublicApiTests +public class KurrentDBPublicApiTests { [Fact] - public void AddEventStoreShouldThrowWhenBuilderIsNull() + public void AddKurrentDBShouldThrowWhenBuilderIsNull() { IDistributedApplicationBuilder builder = null!; - const string name = "eventstore"; + const string name = "kurrentdb"; - var action = () => builder.AddEventStore(name); + var action = () => builder.AddKurrentDB(name); var exception = Assert.Throws(action); Assert.Equal(nameof(builder), exception.ParamName); } [Fact] - public void AddEventStoreShouldThrowWhenNameIsNull() + public void AddKurrentDBShouldThrowWhenNameIsNull() { var builder = new DistributedApplicationBuilder([]); string name = null!; - var action = () => builder.AddEventStore(name); + var action = () => builder.AddKurrentDB(name); var exception = Assert.Throws(action); Assert.Equal(nameof(name), exception.ParamName); @@ -36,9 +36,9 @@ public void AddEventStoreShouldThrowWhenNameIsNull() [InlineData(true)] public void WithDataShouldThrowWhenBuilderIsNull(bool useVolume) { - IResourceBuilder builder = null!; + IResourceBuilder builder = null!; - Func>? action = null; + Func>? action = null; if (useVolume) { @@ -59,11 +59,11 @@ public void WithDataShouldThrowWhenBuilderIsNull(bool useVolume) public void WithDataBindMountShouldThrowWhenSourceIsNull() { var builder = new DistributedApplicationBuilder([]); - var eventstore = builder.AddEventStore("eventstore"); + var kurrentdb = builder.AddKurrentDB("kurrentdb"); string source = null!; - var action = () => eventstore.WithDataBindMount(source); + var action = () => kurrentdb.WithDataBindMount(source); var exception = Assert.Throws(action); Assert.Equal(nameof(source), exception.ParamName); @@ -73,66 +73,66 @@ public void WithDataBindMountShouldThrowWhenSourceIsNull() public void WithDataVolumeShouldAddMountAnnotation() { var builder = new DistributedApplicationBuilder([]); - var eventstore = builder.AddEventStore("eventstore") + var kurrentdb = builder.AddKurrentDB("kurrentdb") .WithDataVolume(name: null); using var app = builder.Build(); var appModel = app.Services.GetRequiredService(); - var resource = appModel.Resources.OfType().SingleOrDefault(); + var resource = appModel.Resources.OfType().SingleOrDefault(); Assert.NotNull(resource); - Assert.Equal("eventstore", resource.Name); + Assert.Equal("kurrentdb", resource.Name); Assert.True(resource.TryGetLastAnnotation(out ContainerMountAnnotation? mountAnnotation)); Assert.EndsWith("-data", mountAnnotation.Source); - Assert.Equal("/var/lib/eventstore", mountAnnotation.Target); + Assert.Equal("/var/lib/kurrentdb", mountAnnotation.Target); } [Fact] public void WithNamedDataVolumeShouldAddMountAnnotation() { var builder = new DistributedApplicationBuilder([]); - var eventstore = builder.AddEventStore("eventstore") + var kurrentdb = builder.AddKurrentDB("kurrentdb") .WithDataVolume("mydata"); using var app = builder.Build(); var appModel = app.Services.GetRequiredService(); - var resource = appModel.Resources.OfType().SingleOrDefault(); + var resource = appModel.Resources.OfType().SingleOrDefault(); Assert.NotNull(resource); - Assert.Equal("eventstore", resource.Name); + Assert.Equal("kurrentdb", resource.Name); Assert.True(resource.TryGetLastAnnotation(out ContainerMountAnnotation? mountAnnotation)); Assert.Equal("mydata", mountAnnotation.Source); - Assert.Equal("/var/lib/eventstore", mountAnnotation.Target); + Assert.Equal("/var/lib/kurrentdb", mountAnnotation.Target); } [Fact] public void WithDataBindMountShouldAddMountAnnotation() { var builder = new DistributedApplicationBuilder([]); - var eventstore = builder.AddEventStore("eventstore") + var kurrentdb = builder.AddKurrentDB("kurrentdb") .WithDataBindMount("./mydata"); using var app = builder.Build(); var appModel = app.Services.GetRequiredService(); - var resource = appModel.Resources.OfType().SingleOrDefault(); + var resource = appModel.Resources.OfType().SingleOrDefault(); Assert.NotNull(resource); - Assert.Equal("eventstore", resource.Name); + Assert.Equal("kurrentdb", resource.Name); Assert.True(resource.TryGetLastAnnotation(out ContainerMountAnnotation? mountAnnotation)); Assert.EndsWith("mydata", mountAnnotation.Source); - Assert.Equal("/var/lib/eventstore", mountAnnotation.Target); + Assert.Equal("/var/lib/kurrentdb", mountAnnotation.Target); } [Fact] - public void EventStoreResourceCtorShouldThrowWhenNameIsNull() + public void KurrentDBResourceCtorShouldThrowWhenNameIsNull() { var builder = new DistributedApplicationBuilder([]); const string name = null!; - var action = () => new EventStoreResource(name); + var action = () => new KurrentDBResource(name); var exception = Assert.Throws(action); Assert.Equal(nameof(name), exception.ParamName); diff --git a/tests/CommunityToolkit.Aspire.EventStore.Tests/AspireEventStoreClientExtensionsTest.cs b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/AspireKurrentDBClientExtensionsTest.cs similarity index 62% rename from tests/CommunityToolkit.Aspire.EventStore.Tests/AspireEventStoreClientExtensionsTest.cs rename to tests/CommunityToolkit.Aspire.KurrentDB.Tests/AspireKurrentDBClientExtensionsTest.cs index adf1307bf..357252acc 100644 --- a/tests/CommunityToolkit.Aspire.EventStore.Tests/AspireEventStoreClientExtensionsTest.cs +++ b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/AspireKurrentDBClientExtensionsTest.cs @@ -1,17 +1,17 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Components.Common.Tests; -using EventStore.Client; +using KurrentDB.Client; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; -namespace CommunityToolkit.Aspire.EventStore.Tests; +namespace CommunityToolkit.Aspire.KurrentDB.Tests; -public class AspireEventStoreClientExtensionsTest(EventStoreContainerFixture containerFixture) : IClassFixture +public class AspireKurrentDBClientExtensionsTest(KurrentDBContainerFixture containerFixture) : IClassFixture { - private const string DefaultConnectionName = "eventstore"; + private const string DefaultConnectionName = "kurrentdb"; private string DefaultConnectionString => RequiresDockerAttribute.IsSupported ? containerFixture.GetConnectionString() : "esdb://localhost:2113?tls=false"; @@ -20,7 +20,7 @@ public class AspireEventStoreClientExtensionsTest(EventStoreContainerFixture con [InlineData(true)] [InlineData(false)] [RequiresDocker] - public async Task AddEventStoreClient_HealthCheckShouldBeRegisteredWhenEnabled(bool useKeyed) + public async Task AddKurrentDBClient_HealthCheckShouldBeRegisteredWhenEnabled(bool useKeyed) { var key = DefaultConnectionName; @@ -28,14 +28,14 @@ public async Task AddEventStoreClient_HealthCheckShouldBeRegisteredWhenEnabled(b if (useKeyed) { - builder.AddKeyedEventStoreClient(key, settings => + builder.AddKeyedKurrentDBClient(key, settings => { settings.DisableHealthChecks = false; }); } else { - builder.AddEventStoreClient(DefaultConnectionName, settings => + builder.AddKurrentDBClient(DefaultConnectionName, settings => { settings.DisableHealthChecks = false; }); @@ -47,7 +47,7 @@ public async Task AddEventStoreClient_HealthCheckShouldBeRegisteredWhenEnabled(b var healthCheckReport = await healthCheckService.CheckHealthAsync(); - var healthCheckName = useKeyed ? $"EventStore.Client_{key}" : "EventStore.Client"; + var healthCheckName = useKeyed ? $"KurrentDB.Client_{key}" : "KurrentDB.Client"; Assert.Contains(healthCheckReport.Entries, x => x.Key == healthCheckName); } @@ -57,20 +57,20 @@ public void CanAddMultipleKeyedServices() { var builder = Host.CreateEmptyApplicationBuilder(null); builder.Configuration.AddInMemoryCollection([ - new KeyValuePair("ConnectionStrings:eventstore1", "esdb://localhost:22113?tls=false"), - new KeyValuePair("ConnectionStrings:eventstore2", "esdb://localhost:22114?tls=false"), - new KeyValuePair("ConnectionStrings:eventstore3", "esdb://localhost:22115?tls=false"), + new KeyValuePair("ConnectionStrings:kurrentdb1", "esdb://localhost:22113?tls=false"), + new KeyValuePair("ConnectionStrings:kurrentdb2", "esdb://localhost:22114?tls=false"), + new KeyValuePair("ConnectionStrings:kurrentdb3", "esdb://localhost:22115?tls=false"), ]); - builder.AddEventStoreClient("eventstore1"); - builder.AddKeyedEventStoreClient("eventstore2"); - builder.AddKeyedEventStoreClient("eventstore3"); + builder.AddKurrentDBClient("kurrentdb1"); + builder.AddKeyedKurrentDBClient("kurrentdb2"); + builder.AddKeyedKurrentDBClient("kurrentdb3"); using var host = builder.Build(); - var client1 = host.Services.GetRequiredService(); - var client2 = host.Services.GetRequiredKeyedService("eventstore2"); - var client3 = host.Services.GetRequiredKeyedService("eventstore3"); + var client1 = host.Services.GetRequiredService(); + var client2 = host.Services.GetRequiredKeyedService("kurrentdb2"); + var client3 = host.Services.GetRequiredKeyedService("kurrentdb3"); Assert.NotSame(client1, client2); Assert.NotSame(client1, client3); diff --git a/tests/CommunityToolkit.Aspire.EventStore.Tests/CommunityToolkit.Aspire.EventStore.Tests.csproj b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/CommunityToolkit.Aspire.KurrentDB.Tests.csproj similarity index 72% rename from tests/CommunityToolkit.Aspire.EventStore.Tests/CommunityToolkit.Aspire.EventStore.Tests.csproj rename to tests/CommunityToolkit.Aspire.KurrentDB.Tests/CommunityToolkit.Aspire.KurrentDB.Tests.csproj index fd0b3c3b2..fabc6da45 100644 --- a/tests/CommunityToolkit.Aspire.EventStore.Tests/CommunityToolkit.Aspire.EventStore.Tests.csproj +++ b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/CommunityToolkit.Aspire.KurrentDB.Tests.csproj @@ -1,12 +1,12 @@ - + - + - + diff --git a/tests/CommunityToolkit.Aspire.EventStore.Tests/ConfigurationTests.cs b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/ConfigurationTests.cs similarity index 58% rename from tests/CommunityToolkit.Aspire.EventStore.Tests/ConfigurationTests.cs rename to tests/CommunityToolkit.Aspire.KurrentDB.Tests/ConfigurationTests.cs index e82a370ee..d68a78997 100644 --- a/tests/CommunityToolkit.Aspire.EventStore.Tests/ConfigurationTests.cs +++ b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/ConfigurationTests.cs @@ -1,23 +1,23 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace CommunityToolkit.Aspire.EventStore.Tests; +namespace CommunityToolkit.Aspire.KurrentDB.Tests; public class ConfigurationTests { [Fact] public void ConnectionStringIsNullByDefault() => - Assert.Null(new EventStoreSettings().ConnectionString); + Assert.Null(new KurrentDBSettings().ConnectionString); [Fact] public void HealthChecksEnabledByDefault() => - Assert.False(new EventStoreSettings().DisableHealthChecks); + Assert.False(new KurrentDBSettings().DisableHealthChecks); [Fact] public void HealthCheckTimeoutNullByDefault() => - Assert.Null(new EventStoreSettings().HealthCheckTimeout); + Assert.Null(new KurrentDBSettings().HealthCheckTimeout); [Fact] public void DisableTracingIsFalseByDefault() => - Assert.False(new EventStoreSettings().DisableTracing); + Assert.False(new KurrentDBSettings().DisableTracing); } diff --git a/tests/CommunityToolkit.Aspire.EventStore.Tests/ConformanceTests.cs b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/ConformanceTests.cs similarity index 67% rename from tests/CommunityToolkit.Aspire.EventStore.Tests/ConformanceTests.cs rename to tests/CommunityToolkit.Aspire.KurrentDB.Tests/ConformanceTests.cs index ec2637221..41391480f 100644 --- a/tests/CommunityToolkit.Aspire.EventStore.Tests/ConformanceTests.cs +++ b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/ConformanceTests.cs @@ -3,15 +3,15 @@ using Aspire.Components.Common.Tests; using Aspire.Components.ConformanceTests; -using EventStore.Client; +using KurrentDB.Client; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -namespace CommunityToolkit.Aspire.EventStore.Tests; +namespace CommunityToolkit.Aspire.KurrentDB.Tests; -public class ConformanceTests(EventStoreContainerFixture containerFixture) : ConformanceTests, IClassFixture +public class ConformanceTests(KurrentDBContainerFixture containerFixture) : ConformanceTests, IClassFixture { - private readonly EventStoreContainerFixture _containerFixture = containerFixture; + private readonly KurrentDBContainerFixture _containerFixture = containerFixture; protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Singleton; @@ -31,27 +31,27 @@ protected override void PopulateConfiguration(ConfigurationManager configuration configuration.AddInMemoryCollection( [ - new KeyValuePair($"Aspire:EventStore:Client:ConnectionString", $"{connectionString}"), + new KeyValuePair($"Aspire:KurrentDB:Client:ConnectionString", $"{connectionString}"), new KeyValuePair($"ConnectionStrings:{key}", $"{connectionString}") ]); } - protected override void RegisterComponent(HostApplicationBuilder builder, Action? configure = null, string? key = null) + protected override void RegisterComponent(HostApplicationBuilder builder, Action? configure = null, string? key = null) { if (key is null) { - builder.AddEventStoreClient("eventstore", configureSettings: configure); + builder.AddKurrentDBClient("kurrentdb", configureSettings: configure); } else { - builder.AddKeyedEventStoreClient(key, configureSettings: configure); + builder.AddKeyedKurrentDBClient(key, configureSettings: configure); } } protected override string ValidJsonConfig => """ { "Aspire": { - "EventStore": { + "KurrentDB": { "Client": { "ConnectionString": "esdb://localhost:22113?tls=false", "DisableHealthChecks": "false" @@ -63,25 +63,25 @@ protected override void RegisterComponent(HostApplicationBuilder builder, Action protected override (string json, string error)[] InvalidJsonToErrorMessage => new[] { - ("""{"Aspire": { "EventStore":{ "Client": { "ConnectionString": 3 }}}}""", "Value is \"integer\" but should be \"string\"") + ("""{"Aspire": { "KurrentDB":{ "Client": { "ConnectionString": 3 }}}}""", "Value is \"integer\" but should be \"string\"") }; - protected override void SetHealthCheck(EventStoreSettings options, bool enabled) + protected override void SetHealthCheck(KurrentDBSettings options, bool enabled) { options.DisableHealthChecks = !enabled; } - protected override void SetMetrics(EventStoreSettings options, bool enabled) + protected override void SetMetrics(KurrentDBSettings options, bool enabled) { throw new NotImplementedException(); } - protected override void SetTracing(EventStoreSettings options, bool enabled) + protected override void SetTracing(KurrentDBSettings options, bool enabled) { throw new NotImplementedException(); } - protected override void TriggerActivity(EventStoreClient service) + protected override void TriggerActivity(KurrentDBClient service) { using var source = new CancellationTokenSource(100); diff --git a/tests/CommunityToolkit.Aspire.EventStore.Tests/EventStoreClientPublicApiTests.cs b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/KurrentDBClientPublicApiTests.cs similarity index 61% rename from tests/CommunityToolkit.Aspire.EventStore.Tests/EventStoreClientPublicApiTests.cs rename to tests/CommunityToolkit.Aspire.KurrentDB.Tests/KurrentDBClientPublicApiTests.cs index 19fe11f0b..33327fc69 100644 --- a/tests/CommunityToolkit.Aspire.EventStore.Tests/EventStoreClientPublicApiTests.cs +++ b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/KurrentDBClientPublicApiTests.cs @@ -4,83 +4,83 @@ using Microsoft.Extensions.Hosting; using Xunit; -namespace CommunityToolkit.Aspire.EventStore.Tests; +namespace CommunityToolkit.Aspire.KurrentDB.Tests; -public class EventStoreClientPublicApiTests +public class KurrentDBClientPublicApiTests { [Fact] - public void AddEventStoreClientShouldThrowWhenBuilderIsNull() + public void AddKurrentDBClientShouldThrowWhenBuilderIsNull() { IHostApplicationBuilder builder = null!; - var connectionName = "eventstore"; + var connectionName = "kurrentdb"; - var action = () => builder.AddEventStoreClient(connectionName); + var action = () => builder.AddKurrentDBClient(connectionName); var exception = Assert.Throws(action); Assert.Equal(nameof(builder), exception.ParamName); } [Fact] - public void AddEventStoreClientShouldThrowWhenNameIsNull() + public void AddKurrentDBClientShouldThrowWhenNameIsNull() { var builder = Host.CreateEmptyApplicationBuilder(null); string connectionName = null!; - var action = () => builder.AddEventStoreClient(connectionName); + var action = () => builder.AddKurrentDBClient(connectionName); var exception = Assert.Throws(action); Assert.Equal(nameof(connectionName), exception.ParamName); } [Fact] - public void AddEventStoreClientShouldThrowWhenNameIsEmpty() + public void AddKurrentDBClientShouldThrowWhenNameIsEmpty() { var builder = Host.CreateEmptyApplicationBuilder(null); string connectionName = ""; - var action = () => builder.AddEventStoreClient(connectionName); + var action = () => builder.AddKurrentDBClient(connectionName); var exception = Assert.Throws(action); Assert.Equal(nameof(connectionName), exception.ParamName); } [Fact] - public void AddKeyedEventStoreClientShouldThrowWhenBuilderIsNull() + public void AddKeyedKurrentDBClientShouldThrowWhenBuilderIsNull() { IHostApplicationBuilder builder = null!; - var connectionName = "eventstore"; + var connectionName = "kurrentdb"; - var action = () => builder.AddKeyedEventStoreClient(connectionName); + var action = () => builder.AddKeyedKurrentDBClient(connectionName); var exception = Assert.Throws(action); Assert.Equal(nameof(builder), exception.ParamName); } [Fact] - public void AddKeyedEventStoreClientShouldThrowWhenNameIsNull() + public void AddKeyedKurrentDBClientShouldThrowWhenNameIsNull() { var builder = Host.CreateEmptyApplicationBuilder(null); string name = null!; - var action = () => builder.AddKeyedEventStoreClient(name); + var action = () => builder.AddKeyedKurrentDBClient(name); var exception = Assert.Throws(action); Assert.Equal(nameof(name), exception.ParamName); } [Fact] - public void AddKeyedEventStoreClientShouldThrowWhenNameIsEmpty() + public void AddKeyedKurrentDBClientShouldThrowWhenNameIsEmpty() { var builder = Host.CreateEmptyApplicationBuilder(null); string name = ""; - var action = () => builder.AddKeyedEventStoreClient(name); + var action = () => builder.AddKeyedKurrentDBClient(name); var exception = Assert.Throws(action); Assert.Equal(nameof(name), exception.ParamName); diff --git a/tests/CommunityToolkit.Aspire.EventStore.Tests/EventStoreContainerFixture.cs b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/KurrentDBContainerFixture.cs similarity index 77% rename from tests/CommunityToolkit.Aspire.EventStore.Tests/EventStoreContainerFixture.cs rename to tests/CommunityToolkit.Aspire.KurrentDB.Tests/KurrentDBContainerFixture.cs index e8f07a8d6..b81f2cfc2 100644 --- a/tests/CommunityToolkit.Aspire.EventStore.Tests/EventStoreContainerFixture.cs +++ b/tests/CommunityToolkit.Aspire.KurrentDB.Tests/KurrentDBContainerFixture.cs @@ -1,14 +1,14 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Components.Common.Tests; -using CommunityToolkit.Aspire.Hosting.EventStore; +using CommunityToolkit.Aspire.Hosting.KurrentDB; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; -namespace CommunityToolkit.Aspire.EventStore.Tests; +namespace CommunityToolkit.Aspire.KurrentDB.Tests; -public sealed class EventStoreContainerFixture : IAsyncLifetime +public sealed class KurrentDBContainerFixture : IAsyncLifetime { public IContainer? Container { get; private set; } @@ -27,7 +27,7 @@ public async Task InitializeAsync() if (RequiresDockerAttribute.IsSupported) { Container = new ContainerBuilder() - .WithImage($"{EventStoreContainerImageTags.Registry}/{EventStoreContainerImageTags.Image}:{EventStoreContainerImageTags.Tag}") + .WithImage($"{KurrentDBContainerImageTags.Registry}/{KurrentDBContainerImageTags.Image}:{KurrentDBContainerImageTags.Tag}") .WithPortBinding(2113, true) .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(r => r.ForPort(2113))) .WithEnvironment("EVENTSTORE_CLUSTER_SIZE", "1")