From cfe5d029aa0043e716f22582be887b8315bb4319 Mon Sep 17 00:00:00 2001 From: Alexander Konietzko Date: Wed, 13 Nov 2024 23:46:08 +0100 Subject: [PATCH 01/11] feat: Aspire --- .../CleanArchitecture.Api.csproj | 1 + CleanArchitecture.Api/Program.cs | 4 + .../CleanArchitecture.AppHost.csproj | 22 ++++ CleanArchitecture.AppHost/Program.cs | 5 + .../Properties/launchSettings.json | 29 +++++ .../appsettings.Development.json | 8 ++ CleanArchitecture.AppHost/appsettings.json | 9 ++ .../CleanArchitecture.IntegrationTests.csproj | 2 +- .../CleanArchitecture.ServiceDefaults.csproj | 22 ++++ .../Extensions.cs | 119 ++++++++++++++++++ CleanArchitecture.sln | 16 +++ 11 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 CleanArchitecture.AppHost/CleanArchitecture.AppHost.csproj create mode 100644 CleanArchitecture.AppHost/Program.cs create mode 100644 CleanArchitecture.AppHost/Properties/launchSettings.json create mode 100644 CleanArchitecture.AppHost/appsettings.Development.json create mode 100644 CleanArchitecture.AppHost/appsettings.json create mode 100644 CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj create mode 100644 CleanArchitecture.ServiceDefaults/Extensions.cs diff --git a/CleanArchitecture.Api/CleanArchitecture.Api.csproj b/CleanArchitecture.Api/CleanArchitecture.Api.csproj index b6e744f..862a120 100644 --- a/CleanArchitecture.Api/CleanArchitecture.Api.csproj +++ b/CleanArchitecture.Api/CleanArchitecture.Api.csproj @@ -32,6 +32,7 @@ + diff --git a/CleanArchitecture.Api/Program.cs b/CleanArchitecture.Api/Program.cs index 5fb7fad..4d67701 100644 --- a/CleanArchitecture.Api/Program.cs +++ b/CleanArchitecture.Api/Program.cs @@ -18,6 +18,8 @@ var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + builder.Services.AddControllers(); builder.Services.AddGrpc(); builder.Services.AddGrpcReflection(); @@ -88,6 +90,8 @@ var app = builder.Build(); +app.MapDefaultEndpoints(); + using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; diff --git a/CleanArchitecture.AppHost/CleanArchitecture.AppHost.csproj b/CleanArchitecture.AppHost/CleanArchitecture.AppHost.csproj new file mode 100644 index 0000000..d218d15 --- /dev/null +++ b/CleanArchitecture.AppHost/CleanArchitecture.AppHost.csproj @@ -0,0 +1,22 @@ + + + + + + Exe + net9.0 + enable + enable + true + e7ec3788-69e9-4631-b350-d59657ddd747 + + + + + + + + + + + diff --git a/CleanArchitecture.AppHost/Program.cs b/CleanArchitecture.AppHost/Program.cs new file mode 100644 index 0000000..24d649f --- /dev/null +++ b/CleanArchitecture.AppHost/Program.cs @@ -0,0 +1,5 @@ +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddProject("cleanarchitecture-api"); + +builder.Build().Run(); diff --git a/CleanArchitecture.AppHost/Properties/launchSettings.json b/CleanArchitecture.AppHost/Properties/launchSettings.json new file mode 100644 index 0000000..32376b9 --- /dev/null +++ b/CleanArchitecture.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:17270;http://localhost:15188", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21200", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22111" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15188", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19170", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20198" + } + } + } +} diff --git a/CleanArchitecture.AppHost/appsettings.Development.json b/CleanArchitecture.AppHost/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/CleanArchitecture.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/CleanArchitecture.AppHost/appsettings.json b/CleanArchitecture.AppHost/appsettings.json new file mode 100644 index 0000000..31c092a --- /dev/null +++ b/CleanArchitecture.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj b/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj index 4cca339..7d82a63 100644 --- a/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj +++ b/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj @@ -14,7 +14,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj b/CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj new file mode 100644 index 0000000..e86dfa4 --- /dev/null +++ b/CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net9.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/CleanArchitecture.ServiceDefaults/Extensions.cs b/CleanArchitecture.ServiceDefaults/Extensions.cs new file mode 100644 index 0000000..13151bf --- /dev/null +++ b/CleanArchitecture.ServiceDefaults/Extensions.cs @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ServiceDiscovery; +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/CleanArchitecture.sln b/CleanArchitecture.sln index 3e8b93c..2d516fe 100644 --- a/CleanArchitecture.sln +++ b/CleanArchitecture.sln @@ -29,6 +29,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D3DF9DF5 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Shared", "CleanArchitecture.Shared\CleanArchitecture.Shared.csproj", "{E82B473D-0281-4713-9550-7D3FF7D9CFDE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.AppHost", "CleanArchitecture.AppHost\CleanArchitecture.AppHost.csproj", "{AF8AC381-9A62-49A8-B42D-44BF8B0F28D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.ServiceDefaults", "CleanArchitecture.ServiceDefaults\CleanArchitecture.ServiceDefaults.csproj", "{CED4C7AC-AD5C-4054-A338-95C32945D69E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aspire", "Aspire", "{53D849CC-87DF-4A90-88C1-8380A8C07CB0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -83,6 +89,14 @@ Global {E82B473D-0281-4713-9550-7D3FF7D9CFDE}.Debug|Any CPU.Build.0 = Debug|Any CPU {E82B473D-0281-4713-9550-7D3FF7D9CFDE}.Release|Any CPU.ActiveCfg = Release|Any CPU {E82B473D-0281-4713-9550-7D3FF7D9CFDE}.Release|Any CPU.Build.0 = Release|Any CPU + {AF8AC381-9A62-49A8-B42D-44BF8B0F28D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF8AC381-9A62-49A8-B42D-44BF8B0F28D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF8AC381-9A62-49A8-B42D-44BF8B0F28D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF8AC381-9A62-49A8-B42D-44BF8B0F28D0}.Release|Any CPU.Build.0 = Release|Any CPU + {CED4C7AC-AD5C-4054-A338-95C32945D69E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CED4C7AC-AD5C-4054-A338-95C32945D69E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CED4C7AC-AD5C-4054-A338-95C32945D69E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CED4C7AC-AD5C-4054-A338-95C32945D69E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -93,6 +107,8 @@ Global {EC8122EB-C5E0-452F-B1CE-DA47DAEBC8F2} = {D3DF9DF5-BD7D-48BC-8BE6-DBD0FABB8B45} {39732BD4-909F-410C-8737-1F9FE3E269A7} = {D3DF9DF5-BD7D-48BC-8BE6-DBD0FABB8B45} {E3A836DD-85DB-44FD-BC19-DDFE111D9EB0} = {D3DF9DF5-BD7D-48BC-8BE6-DBD0FABB8B45} + {AF8AC381-9A62-49A8-B42D-44BF8B0F28D0} = {53D849CC-87DF-4A90-88C1-8380A8C07CB0} + {CED4C7AC-AD5C-4054-A338-95C32945D69E} = {53D849CC-87DF-4A90-88C1-8380A8C07CB0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DDAAEEA0-FB1B-4EAD-902B-C12034FFC17A} From 2a048b682a1e1eaf9cd498660c22d752fe59ff30 Mon Sep 17 00:00:00 2001 From: alex289 Date: Thu, 14 Nov 2024 14:29:03 +0100 Subject: [PATCH 02/11] feat: Cleanup redundant configurations --- CleanArchitecture.Api/Program.cs | 1 + .../Properties/launchSettings.json | 16 ----- .../CleanArchitecture.AppHost.csproj | 3 + CleanArchitecture.AppHost/Program.cs | 23 ++++++- .../Properties/launchSettings.json | 17 +---- .../CleanArchitecture.ServiceDefaults.csproj | 1 + .../Extensions.cs | 65 +++++++------------ 7 files changed, 54 insertions(+), 72 deletions(-) diff --git a/CleanArchitecture.Api/Program.cs b/CleanArchitecture.Api/Program.cs index 4d67701..95c2bb1 100644 --- a/CleanArchitecture.Api/Program.cs +++ b/CleanArchitecture.Api/Program.cs @@ -6,6 +6,7 @@ using CleanArchitecture.Domain.Rabbitmq.Extensions; using CleanArchitecture.Infrastructure.Database; using CleanArchitecture.Infrastructure.Extensions; +using CleanArchitecture.ServiceDefaults; using HealthChecks.ApplicationStatus.DependencyInjection; using HealthChecks.UI.Client; using Microsoft.AspNetCore.Builder; diff --git a/CleanArchitecture.Api/Properties/launchSettings.json b/CleanArchitecture.Api/Properties/launchSettings.json index 7a96097..5b148db 100644 --- a/CleanArchitecture.Api/Properties/launchSettings.json +++ b/CleanArchitecture.Api/Properties/launchSettings.json @@ -1,13 +1,5 @@ { "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:38452", - "sslPort": 44309 - } - }, "profiles": { "CleanArchitecture.Api": { "commandName": "Project", @@ -18,14 +10,6 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } } } } diff --git a/CleanArchitecture.AppHost/CleanArchitecture.AppHost.csproj b/CleanArchitecture.AppHost/CleanArchitecture.AppHost.csproj index d218d15..74a66dc 100644 --- a/CleanArchitecture.AppHost/CleanArchitecture.AppHost.csproj +++ b/CleanArchitecture.AppHost/CleanArchitecture.AppHost.csproj @@ -13,6 +13,9 @@ + + + diff --git a/CleanArchitecture.AppHost/Program.cs b/CleanArchitecture.AppHost/Program.cs index 24d649f..261b990 100644 --- a/CleanArchitecture.AppHost/Program.cs +++ b/CleanArchitecture.AppHost/Program.cs @@ -1,5 +1,26 @@ var builder = DistributedApplication.CreateBuilder(args); -builder.AddProject("cleanarchitecture-api"); +var redis = builder.AddRedis("Redis"); + +var rabbitPasswordRessource = new ParameterResource("password", _ => "guest"); +var rabbitPasswordParameter = + builder.AddParameter("username", rabbitPasswordRessource.Value); + +var rabbitMq = builder + .AddRabbitMQ("RabbitMq", null, rabbitPasswordParameter, 5672) + .WithManagementPlugin(); + +var sqlServer = builder.AddSqlServer("SqlServer"); + +builder.AddProject("CleanArchitecture.Api") + .WithHttpsEndpoint(17270) + .WithHealthCheck("Api Health") + .WithOtlpExporter() + .WithReference(redis) + .WaitFor(redis) + .WithReference(rabbitMq) + .WaitFor(rabbitMq) + .WithReference(sqlServer) + .WaitFor(sqlServer); builder.Build().Run(); diff --git a/CleanArchitecture.AppHost/Properties/launchSettings.json b/CleanArchitecture.AppHost/Properties/launchSettings.json index 32376b9..030b6d4 100644 --- a/CleanArchitecture.AppHost/Properties/launchSettings.json +++ b/CleanArchitecture.AppHost/Properties/launchSettings.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { - "https": { + "Aspire": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, @@ -10,19 +10,8 @@ "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21200", - "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22111" - } - }, - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:15188", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "DOTNET_ENVIRONMENT": "Development", - "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19170", - "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20198" + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22111", + "ASPIRE_ENABLED": "true" } } } diff --git a/CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj b/CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj index e86dfa4..ccc2414 100644 --- a/CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj +++ b/CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj @@ -15,6 +15,7 @@ + diff --git a/CleanArchitecture.ServiceDefaults/Extensions.cs b/CleanArchitecture.ServiceDefaults/Extensions.cs index 13151bf..c6dc3d3 100644 --- a/CleanArchitecture.ServiceDefaults/Extensions.cs +++ b/CleanArchitecture.ServiceDefaults/Extensions.cs @@ -2,21 +2,26 @@ using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ServiceDiscovery; using OpenTelemetry; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; -namespace Microsoft.Extensions.Hosting; +namespace CleanArchitecture.ServiceDefaults; -// 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 + private const string AspireEnabled = "ASPIRE_ENABLED"; + + public static void AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder { + if (builder.Configuration[AspireEnabled] != "true") + { + return; + } + builder.ConfigureOpenTelemetry(); builder.AddDefaultHealthChecks(); @@ -25,23 +30,17 @@ public static TBuilder AddServiceDefaults(this TBuilder builder) where 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; + builder.Services.Configure(options => + { + options.AllowedSchemes = ["https"]; + }); } - public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + private static void ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder { builder.Logging.AddOpenTelemetry(logging => { @@ -60,17 +59,14 @@ public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) w { tracing.AddSource(builder.Environment.ApplicationName) .AddAspNetCoreInstrumentation() - // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) - //.AddGrpcClientInstrumentation() + .AddGrpcClientInstrumentation() .AddHttpClientInstrumentation(); }); builder.AddOpenTelemetryExporters(); - - return builder; } - private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + private static void AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder { var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); @@ -78,42 +74,29 @@ private static TBuilder AddOpenTelemetryExporters(this TBuilder builde { 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 + private static void 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) + public static void 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.Configuration[AspireEnabled] != "true") + { + return; + } + 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; } } From d9ae278caf7732259c5cd9925cfd942f22059f41 Mon Sep 17 00:00:00 2001 From: alex289 Date: Thu, 14 Nov 2024 22:15:14 +0100 Subject: [PATCH 03/11] fix: Use correct connection string --- CleanArchitecture.Api/Program.cs | 34 ++++++++++++------- CleanArchitecture.AppHost/Program.cs | 8 ++--- .../Extensions/ServiceCollectionExtensions.cs | 8 ++--- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/CleanArchitecture.Api/Program.cs b/CleanArchitecture.Api/Program.cs index 95c2bb1..5efc5d6 100644 --- a/CleanArchitecture.Api/Program.cs +++ b/CleanArchitecture.Api/Program.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -31,32 +30,41 @@ .AddDbContextCheck() .AddApplicationStatus(); +var isAspire = builder.Configuration["ASPIRE_ENABLED"] == "true"; + +var rabbitHost = builder.Configuration["RabbitMQ:Host"]; +var rabbitPort = builder.Configuration["RabbitMQ:Port"]; +var rabbitUser = builder.Configuration["RabbitMQ:Username"]; +var rabbitPass = builder.Configuration["RabbitMQ:Password"]; +var rabbitConnectionString = + isAspire ? builder.Configuration["ConnectionStrings:RabbitMq"] : + $"amqp://{rabbitUser}:{rabbitPass}@{rabbitHost}:{rabbitPort}"; + +var redisConnectionString = isAspire ? builder.Configuration["ConnectionStrings:Redis"] : builder.Configuration["RedisHostName"]; + +var dbConnectionString = isAspire ? builder.Configuration["ConnectionStrings:Database"] : builder.Configuration["ConnectionStrings:DefaultConnection"]; + if (builder.Environment.IsProduction()) { - var rabbitHost = builder.Configuration["RabbitMQ:Host"]; - var rabbitPort = builder.Configuration["RabbitMQ:Port"]; - var rabbitUser = builder.Configuration["RabbitMQ:Username"]; - var rabbitPass = builder.Configuration["RabbitMQ:Password"]; - builder.Services .AddHealthChecks() - .AddSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")!) - .AddRedis(builder.Configuration["RedisHostName"]!, "Redis") + .AddSqlServer(dbConnectionString!) + .AddRedis(redisConnectionString!, "Redis") .AddRabbitMQ( - $"amqp://{rabbitUser}:{rabbitPass}@{rabbitHost}:{rabbitPort}", + rabbitConnectionString!, name: "RabbitMQ"); } builder.Services.AddDbContext(options => { options.UseLazyLoadingProxies(); - options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"), + options.UseSqlServer(dbConnectionString, b => b.MigrationsAssembly("CleanArchitecture.Infrastructure")); }); builder.Services.AddSwagger(); builder.Services.AddAuth(builder.Configuration); -builder.Services.AddInfrastructure(builder.Configuration, "CleanArchitecture.Infrastructure"); +builder.Services.AddInfrastructure("CleanArchitecture.Infrastructure", dbConnectionString!); builder.Services.AddQueryHandlers(); builder.Services.AddServices(); builder.Services.AddSortProviders(); @@ -76,11 +84,11 @@ console.IncludeScopes = true; })); -if (builder.Environment.IsProduction() || !string.IsNullOrWhiteSpace(builder.Configuration["RedisHostName"])) +if (builder.Environment.IsProduction() || !string.IsNullOrWhiteSpace(redisConnectionString)) { builder.Services.AddStackExchangeRedisCache(options => { - options.Configuration = builder.Configuration["RedisHostName"]; + options.Configuration = redisConnectionString; options.InstanceName = "clean-architecture"; }); } diff --git a/CleanArchitecture.AppHost/Program.cs b/CleanArchitecture.AppHost/Program.cs index 261b990..737d164 100644 --- a/CleanArchitecture.AppHost/Program.cs +++ b/CleanArchitecture.AppHost/Program.cs @@ -11,16 +11,16 @@ .WithManagementPlugin(); var sqlServer = builder.AddSqlServer("SqlServer"); +var db = sqlServer.AddDatabase("Database", "clean-architecture"); -builder.AddProject("CleanArchitecture.Api") - .WithHttpsEndpoint(17270) - .WithHealthCheck("Api Health") +builder.AddProject("CleanArchitecture-Api") + //.WithHealthCheck("Api Health") .WithOtlpExporter() .WithReference(redis) .WaitFor(redis) .WithReference(rabbitMq) .WaitFor(rabbitMq) - .WithReference(sqlServer) + .WithReference(db) .WaitFor(sqlServer); builder.Build().Run(); diff --git a/CleanArchitecture.Infrastructure/Extensions/ServiceCollectionExtensions.cs b/CleanArchitecture.Infrastructure/Extensions/ServiceCollectionExtensions.cs index ad8ebac..994058f 100644 --- a/CleanArchitecture.Infrastructure/Extensions/ServiceCollectionExtensions.cs +++ b/CleanArchitecture.Infrastructure/Extensions/ServiceCollectionExtensions.cs @@ -7,7 +7,6 @@ using CleanArchitecture.Infrastructure.Repositories; using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace CleanArchitecture.Infrastructure.Extensions; @@ -16,16 +15,15 @@ public static class ServiceCollectionExtensions { public static IServiceCollection AddInfrastructure( this IServiceCollection services, - IConfiguration configuration, string migrationsAssemblyName, - string connectionStringName = "DefaultConnection") + string connectionString) { // Add event store db context services.AddDbContext( options => { options.UseSqlServer( - configuration.GetConnectionString(connectionStringName), + connectionString, b => b.MigrationsAssembly(migrationsAssemblyName)); }); @@ -33,7 +31,7 @@ public static IServiceCollection AddInfrastructure( options => { options.UseSqlServer( - configuration.GetConnectionString(connectionStringName), + connectionString, b => b.MigrationsAssembly(migrationsAssemblyName)); }); From 7d0ac810462f084f94579d6548d9b812ee822597 Mon Sep 17 00:00:00 2001 From: alex289 Date: Thu, 14 Nov 2024 22:17:36 +0100 Subject: [PATCH 04/11] feat: Update redis and rabbit image --- docker-compose.yml | 4 ++-- k8s-deployments/rabbitmq.yml | 2 +- k8s-deployments/redis.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e0c871b..075b50f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,7 +37,7 @@ services: - 1433:1433 redis: - image: docker.io/bitnami/redis:7.2 + image: docker.io/bitnami/redis:7.4 environment: # ALLOW_EMPTY_PASSWORD is recommended only for development. - ALLOW_EMPTY_PASSWORD=yes @@ -48,7 +48,7 @@ services: - 'redis_data:/bitnami/redis/data' rabbitmq: - image: "rabbitmq:3-management" + image: "rabbitmq:4-management" ports: - 5672:5672 - 15672:15672 diff --git a/k8s-deployments/rabbitmq.yml b/k8s-deployments/rabbitmq.yml index bfef599..bb52301 100644 --- a/k8s-deployments/rabbitmq.yml +++ b/k8s-deployments/rabbitmq.yml @@ -32,7 +32,7 @@ spec: spec: containers: - name: rabbitmq - image: rabbitmq:management + image: rabbitmq:4-management ports: - containerPort: 5672 - containerPort: 15672 diff --git a/k8s-deployments/redis.yml b/k8s-deployments/redis.yml index 4055e86..6196e1a 100644 --- a/k8s-deployments/redis.yml +++ b/k8s-deployments/redis.yml @@ -27,7 +27,7 @@ spec: spec: containers: - name: redis - image: docker.io/bitnami/redis:7.2 + image: docker.io/bitnami/redis:7.4 env: # ALLOW_EMPTY_PASSWORD is recommended only for development. - name: ALLOW_EMPTY_PASSWORD From 425da0b4503a694024a7ad454c0ae55041601c05 Mon Sep 17 00:00:00 2001 From: alex289 Date: Wed, 20 Nov 2024 21:25:45 +0100 Subject: [PATCH 05/11] feat: Add entity framework telemetry --- CleanArchitecture.AppHost/Program.cs | 1 - .../CleanArchitecture.ServiceDefaults.csproj | 1 + CleanArchitecture.ServiceDefaults/Extensions.cs | 7 ++++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CleanArchitecture.AppHost/Program.cs b/CleanArchitecture.AppHost/Program.cs index 737d164..607445d 100644 --- a/CleanArchitecture.AppHost/Program.cs +++ b/CleanArchitecture.AppHost/Program.cs @@ -14,7 +14,6 @@ var db = sqlServer.AddDatabase("Database", "clean-architecture"); builder.AddProject("CleanArchitecture-Api") - //.WithHealthCheck("Api Health") .WithOtlpExporter() .WithReference(redis) .WaitFor(redis) diff --git a/CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj b/CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj index ccc2414..f0eaa55 100644 --- a/CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj +++ b/CleanArchitecture.ServiceDefaults/CleanArchitecture.ServiceDefaults.csproj @@ -15,6 +15,7 @@ + diff --git a/CleanArchitecture.ServiceDefaults/Extensions.cs b/CleanArchitecture.ServiceDefaults/Extensions.cs index c6dc3d3..8705c03 100644 --- a/CleanArchitecture.ServiceDefaults/Extensions.cs +++ b/CleanArchitecture.ServiceDefaults/Extensions.cs @@ -14,14 +14,14 @@ namespace CleanArchitecture.ServiceDefaults; public static class Extensions { private const string AspireEnabled = "ASPIRE_ENABLED"; - + public static void AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder { if (builder.Configuration[AspireEnabled] != "true") { return; } - + builder.ConfigureOpenTelemetry(); builder.AddDefaultHealthChecks(); @@ -60,6 +60,7 @@ private static void ConfigureOpenTelemetry(this TBuilder builder) wher tracing.AddSource(builder.Environment.ApplicationName) .AddAspNetCoreInstrumentation() .AddGrpcClientInstrumentation() + .AddEntityFrameworkCoreInstrumentation() .AddHttpClientInstrumentation(); }); @@ -88,7 +89,7 @@ public static void MapDefaultEndpoints(this WebApplication app) { return; } - + if (app.Environment.IsDevelopment()) { app.MapHealthChecks("/health"); From 693ca4589d558fa71835410e6d0c8c6d4c0d9644 Mon Sep 17 00:00:00 2001 From: alex289 Date: Thu, 21 Nov 2024 22:08:39 +0100 Subject: [PATCH 06/11] feat: Fix rabbitmq registration --- .../Extensions/ConfigurationExtensions.cs | 41 +++++++++++++++++++ CleanArchitecture.Api/Program.cs | 14 ++----- .../Extensions/ServiceCollectionExtensions.cs | 7 +--- .../Rabbitmq/RabbitMqConfiguration.cs | 2 + .../Rabbitmq/RabbitMqHandler.cs | 4 ++ 5 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 CleanArchitecture.Api/Extensions/ConfigurationExtensions.cs diff --git a/CleanArchitecture.Api/Extensions/ConfigurationExtensions.cs b/CleanArchitecture.Api/Extensions/ConfigurationExtensions.cs new file mode 100644 index 0000000..bbe88c7 --- /dev/null +++ b/CleanArchitecture.Api/Extensions/ConfigurationExtensions.cs @@ -0,0 +1,41 @@ +using System; +using CleanArchitecture.Domain.Rabbitmq; +using Microsoft.Extensions.Configuration; + +namespace CleanArchitecture.Api.Extensions; + +public static class ConfigurationExtensions +{ + public static RabbitMqConfiguration GetRabbitMqConfiguration( + this IConfiguration configuration) + { + var isAspire = configuration["ASPIRE_ENABLED"] == "true"; + + var rabbitEnabled = configuration["RabbitMQ:Enabled"]; + var rabbitHost = configuration["RabbitMQ:Host"]; + var rabbitPort = configuration["RabbitMQ:Port"]; + var rabbitUser = configuration["RabbitMQ:Username"]; + var rabbitPass = configuration["RabbitMQ:Password"]; + + if (isAspire) + { + rabbitEnabled = "true"; + var connectionString = configuration["ConnectionStrings:RabbitMq"]; + + var rabbitUri = new Uri(connectionString!); + rabbitHost = rabbitUri.Host; + rabbitPort = rabbitUri.Port.ToString(); + rabbitUser = rabbitUri.UserInfo.Split(':')[0]; + rabbitPass = rabbitUri.UserInfo.Split(':')[1]; + } + + return new RabbitMqConfiguration() + { + Host = rabbitHost ?? "", + Port = int.Parse(rabbitPort ?? "0"), + Enabled = bool.Parse(rabbitEnabled ?? "false"), + Username = rabbitUser ?? "", + Password = rabbitPass ?? "", + }; + } +} \ No newline at end of file diff --git a/CleanArchitecture.Api/Program.cs b/CleanArchitecture.Api/Program.cs index 5efc5d6..e0410b8 100644 --- a/CleanArchitecture.Api/Program.cs +++ b/CleanArchitecture.Api/Program.cs @@ -32,16 +32,8 @@ var isAspire = builder.Configuration["ASPIRE_ENABLED"] == "true"; -var rabbitHost = builder.Configuration["RabbitMQ:Host"]; -var rabbitPort = builder.Configuration["RabbitMQ:Port"]; -var rabbitUser = builder.Configuration["RabbitMQ:Username"]; -var rabbitPass = builder.Configuration["RabbitMQ:Password"]; -var rabbitConnectionString = - isAspire ? builder.Configuration["ConnectionStrings:RabbitMq"] : - $"amqp://{rabbitUser}:{rabbitPass}@{rabbitHost}:{rabbitPort}"; - +var rabbitConfiguration = builder.Configuration.GetRabbitMqConfiguration(); var redisConnectionString = isAspire ? builder.Configuration["ConnectionStrings:Redis"] : builder.Configuration["RedisHostName"]; - var dbConnectionString = isAspire ? builder.Configuration["ConnectionStrings:Database"] : builder.Configuration["ConnectionStrings:DefaultConnection"]; if (builder.Environment.IsProduction()) @@ -51,7 +43,7 @@ .AddSqlServer(dbConnectionString!) .AddRedis(redisConnectionString!, "Redis") .AddRabbitMQ( - rabbitConnectionString!, + rabbitConfiguration.ConnectionString, name: "RabbitMQ"); } @@ -72,7 +64,7 @@ builder.Services.AddNotificationHandlers(); builder.Services.AddApiUser(); -builder.Services.AddRabbitMqHandler(builder.Configuration, "RabbitMQ"); +builder.Services.AddRabbitMqHandler(rabbitConfiguration); builder.Services.AddHostedService(); diff --git a/CleanArchitecture.Domain/Rabbitmq/Extensions/ServiceCollectionExtensions.cs b/CleanArchitecture.Domain/Rabbitmq/Extensions/ServiceCollectionExtensions.cs index 42ba1ca..21b7413 100644 --- a/CleanArchitecture.Domain/Rabbitmq/Extensions/ServiceCollectionExtensions.cs +++ b/CleanArchitecture.Domain/Rabbitmq/Extensions/ServiceCollectionExtensions.cs @@ -7,12 +7,9 @@ public static class ServiceCollectionExtensions { public static IServiceCollection AddRabbitMqHandler( this IServiceCollection services, - IConfiguration configuration, - string rabbitMqConfigSection) + RabbitMqConfiguration configuration) { - var rabbitMq = new RabbitMqConfiguration(); - configuration.Bind(rabbitMqConfigSection, rabbitMq); - services.AddSingleton(rabbitMq); + services.AddSingleton(configuration); services.AddSingleton(); services.AddHostedService(serviceProvider => serviceProvider.GetService()!); diff --git a/CleanArchitecture.Domain/Rabbitmq/RabbitMqConfiguration.cs b/CleanArchitecture.Domain/Rabbitmq/RabbitMqConfiguration.cs index e293b6f..b5771e4 100644 --- a/CleanArchitecture.Domain/Rabbitmq/RabbitMqConfiguration.cs +++ b/CleanArchitecture.Domain/Rabbitmq/RabbitMqConfiguration.cs @@ -7,4 +7,6 @@ public sealed class RabbitMqConfiguration public bool Enabled { get; set; } public string Username { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; + + public string ConnectionString => $"amqp://{Username}:{Password}@{Host}:{Port}"; } \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs b/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs index 4fcb983..2f8b80f 100644 --- a/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs +++ b/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs @@ -38,6 +38,8 @@ public override async Task StartAsync(CancellationToken cancellationToken) return; } + _logger.LogInformation("Starting RabbitMQ connection"); + var factory = new ConnectionFactory { AutomaticRecoveryEnabled = true, @@ -49,6 +51,8 @@ public override async Task StartAsync(CancellationToken cancellationToken) var connection = await factory.CreateConnectionAsync(cancellationToken); _channel = await connection.CreateChannelAsync(null, cancellationToken); + + await base.StartAsync(cancellationToken); } From 7a166d15c48956a95ed7bd74deac4c47367df376 Mon Sep 17 00:00:00 2001 From: alex289 Date: Thu, 21 Nov 2024 22:10:59 +0100 Subject: [PATCH 07/11] chore: Code cleanup --- .../Extensions/ConfigurationExtensions.cs | 2 +- CleanArchitecture.Api/Program.cs | 7 +++++-- CleanArchitecture.AppHost/Program.cs | 4 ++-- .../Rabbitmq/Extensions/ServiceCollectionExtensions.cs | 1 - CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs | 9 +++++---- CleanArchitecture.ServiceDefaults/Extensions.cs | 10 ++++------ 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/CleanArchitecture.Api/Extensions/ConfigurationExtensions.cs b/CleanArchitecture.Api/Extensions/ConfigurationExtensions.cs index bbe88c7..a539f68 100644 --- a/CleanArchitecture.Api/Extensions/ConfigurationExtensions.cs +++ b/CleanArchitecture.Api/Extensions/ConfigurationExtensions.cs @@ -35,7 +35,7 @@ public static RabbitMqConfiguration GetRabbitMqConfiguration( Port = int.Parse(rabbitPort ?? "0"), Enabled = bool.Parse(rabbitEnabled ?? "false"), Username = rabbitUser ?? "", - Password = rabbitPass ?? "", + Password = rabbitPass ?? "" }; } } \ No newline at end of file diff --git a/CleanArchitecture.Api/Program.cs b/CleanArchitecture.Api/Program.cs index e0410b8..37a2c17 100644 --- a/CleanArchitecture.Api/Program.cs +++ b/CleanArchitecture.Api/Program.cs @@ -33,8 +33,11 @@ var isAspire = builder.Configuration["ASPIRE_ENABLED"] == "true"; var rabbitConfiguration = builder.Configuration.GetRabbitMqConfiguration(); -var redisConnectionString = isAspire ? builder.Configuration["ConnectionStrings:Redis"] : builder.Configuration["RedisHostName"]; -var dbConnectionString = isAspire ? builder.Configuration["ConnectionStrings:Database"] : builder.Configuration["ConnectionStrings:DefaultConnection"]; +var redisConnectionString = + isAspire ? builder.Configuration["ConnectionStrings:Redis"] : builder.Configuration["RedisHostName"]; +var dbConnectionString = isAspire + ? builder.Configuration["ConnectionStrings:Database"] + : builder.Configuration["ConnectionStrings:DefaultConnection"]; if (builder.Environment.IsProduction()) { diff --git a/CleanArchitecture.AppHost/Program.cs b/CleanArchitecture.AppHost/Program.cs index 607445d..4780d21 100644 --- a/CleanArchitecture.AppHost/Program.cs +++ b/CleanArchitecture.AppHost/Program.cs @@ -3,7 +3,7 @@ var redis = builder.AddRedis("Redis"); var rabbitPasswordRessource = new ParameterResource("password", _ => "guest"); -var rabbitPasswordParameter = +var rabbitPasswordParameter = builder.AddParameter("username", rabbitPasswordRessource.Value); var rabbitMq = builder @@ -22,4 +22,4 @@ .WithReference(db) .WaitFor(sqlServer); -builder.Build().Run(); +builder.Build().Run(); \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Extensions/ServiceCollectionExtensions.cs b/CleanArchitecture.Domain/Rabbitmq/Extensions/ServiceCollectionExtensions.cs index 21b7413..a785525 100644 --- a/CleanArchitecture.Domain/Rabbitmq/Extensions/ServiceCollectionExtensions.cs +++ b/CleanArchitecture.Domain/Rabbitmq/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,3 @@ -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace CleanArchitecture.Domain.Rabbitmq.Extensions; diff --git a/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs b/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs index 2f8b80f..1d70e99 100644 --- a/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs +++ b/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs @@ -13,7 +13,6 @@ namespace CleanArchitecture.Domain.Rabbitmq; public sealed class RabbitMqHandler : BackgroundService { - private IChannel? _channel; private readonly RabbitMqConfiguration _configuration; private readonly ConcurrentDictionary> _consumers = new(); @@ -21,6 +20,7 @@ public sealed class RabbitMqHandler : BackgroundService private readonly ILogger _logger; private readonly ConcurrentQueue _pendingActions = new(); + private IChannel? _channel; public RabbitMqHandler( RabbitMqConfiguration configuration, @@ -46,7 +46,7 @@ public override async Task StartAsync(CancellationToken cancellationToken) HostName = _configuration.Host, Port = _configuration.Port, UserName = _configuration.Username, - Password = _configuration.Password, + Password = _configuration.Password }; var connection = await factory.CreateConnectionAsync(cancellationToken); @@ -133,14 +133,15 @@ public void AddExchangeConsumer(string exchange, string queue, ConsumeEventHandl AddExchangeConsumer(exchange, string.Empty, queue, consumer); } - private async Task AddEventConsumer(string exchange, string queueName, string routingKey, ConsumeEventHandler consumer) + private async Task AddEventConsumer(string exchange, string queueName, string routingKey, + ConsumeEventHandler consumer) { if (!_configuration.Enabled) { _logger.LogInformation("RabbitMQ is disabled. Event consumer will not be added."); return; } - + var key = $"{exchange}-{routingKey}"; if (!_consumers.TryGetValue(key, out var consumers)) diff --git a/CleanArchitecture.ServiceDefaults/Extensions.cs b/CleanArchitecture.ServiceDefaults/Extensions.cs index 8705c03..9d1b110 100644 --- a/CleanArchitecture.ServiceDefaults/Extensions.cs +++ b/CleanArchitecture.ServiceDefaults/Extensions.cs @@ -34,10 +34,7 @@ public static void AddServiceDefaults(this TBuilder builder) where TBu http.AddServiceDiscovery(); }); - builder.Services.Configure(options => - { - options.AllowedSchemes = ["https"]; - }); + builder.Services.Configure(options => { options.AllowedSchemes = ["https"]; }); } private static void ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder @@ -67,7 +64,8 @@ private static void ConfigureOpenTelemetry(this TBuilder builder) wher builder.AddOpenTelemetryExporters(); } - private static void AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + private static void AddOpenTelemetryExporters(this TBuilder builder) + where TBuilder : IHostApplicationBuilder { var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); @@ -100,4 +98,4 @@ public static void MapDefaultEndpoints(this WebApplication app) }); } } -} +} \ No newline at end of file From 189b9004b712fc23705de8c0ada02fe9a6a728c5 Mon Sep 17 00:00:00 2001 From: alex289 Date: Thu, 21 Nov 2024 22:16:00 +0100 Subject: [PATCH 08/11] feat: Extend documentation for aspire --- Readme.md | 10 +++++++--- k8s-deployments/clean-architecture.yml | 10 +++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 357aa6a..764c9f6 100644 --- a/Readme.md +++ b/Readme.md @@ -25,17 +25,21 @@ The project uses the following dependencies: - **gRPC**: gRPC is an open-source remote procedure call framework that enables efficient communication between distributed systems using a variety of programming languages and protocols. ## Running the Project -To run the project, follow these steps: +To run the project, follow these steps: 1. Clone the repository to your local machine. 2. Open the solution in your IDE of choice. 3. Build the solution to restore the dependencies. 4. Update the connection string in the appsettings.json file to point to your database. -5. Start the API project +5. Start the API project (Alterntively you can use the `dotnet run --project CleanArchitecture.Api` command) 6. The database migrations will be automatically applied on start-up. If the database does not exist, it will be created. 7. The API should be accessible at `https://localhost:/api/` where `` is the port number specified in the project properties and `` is the name of the API controller. +### Using Aspire + +1. Run `dotnet run --project CleanArchitecture.AppHost` in the root directory of the project. + ### Using docker Requirements @@ -53,7 +57,7 @@ options.ConfigurationOptions = new ConfigurationOptions Running the container 1. Build the Dockerfile: `docker build -t clean-architecture .` -2. Run the Container: `docker run -p 80:80 clean-architecture` +2. Run the Container: `docker run -p 80:80 -p 8080:8080 clean-architecture` ### Using docker-compose diff --git a/k8s-deployments/clean-architecture.yml b/k8s-deployments/clean-architecture.yml index 92a8910..dc48762 100644 --- a/k8s-deployments/clean-architecture.yml +++ b/k8s-deployments/clean-architecture.yml @@ -6,9 +6,14 @@ spec: selector: app: clean-architecture-app ports: - - protocol: TCP + - name: http + protocol: TCP port: 80 targetPort: 80 + - name: grpc + protocol: TCP + port: 8080 + targetPort: 8080 type: LoadBalancer --- @@ -32,6 +37,9 @@ spec: image: alexdev28/clean-architecture:latest ports: - containerPort: 80 + protocol: TCP + - containerPort: 8080 + protocol: TCP env: - name: ASPNETCORE_HTTP_PORTS value: 80 From aa4c16922d29f02c6c9b304c4bfc154997ab4c42 Mon Sep 17 00:00:00 2001 From: alex289 Date: Fri, 22 Nov 2024 13:29:34 +0100 Subject: [PATCH 09/11] fix: Docker and kubernetes setup --- Readme.md | 9 +++++---- docker-compose.yml | 3 +-- k8s-deployments/clean-architecture.yml | 2 +- k8s-deployments/redis.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index 764c9f6..0ae2fc3 100644 --- a/Readme.md +++ b/Readme.md @@ -44,8 +44,10 @@ To run the project, follow these steps: Requirements > This is only needed if running the API locally or only the docker image -1. Redis: `docker run --name redis -d -p 6379:6379 -e ALLOW_EMPTY_PASSWORD=yes redis:latest` -2. Add this to the redis configuration in the Program.cs +1. SqlServer: `docker run --name sqlserver -d -p 1433:1433 -e ACCEPT_EULA=Y -e SA_PASSWORD='Password123!#' mcr.microsoft.com/mssql/server` +1. RabbitMq: `docker run --name rabbitmq -d -p 5672:5672 -p 15672:15672 rabbitmq:4-management` +3. Redis: `docker run --name redis -d -p 6379:6379 -e ALLOW_EMPTY_PASSWORD=yes redis:latest` +4. Add this to the redis configuration in the Program.cs ```csharp options.ConfigurationOptions = new ConfigurationOptions { @@ -53,11 +55,10 @@ options.ConfigurationOptions = new ConfigurationOptions EndPoints = { "localhost", "6379" } }; ``` -3. RabbitMq: `docker run --name rabbitmq -d -p 5672:5672 -p 15672:15672 rabbitmq:3-management` Running the container 1. Build the Dockerfile: `docker build -t clean-architecture .` -2. Run the Container: `docker run -p 80:80 -p 8080:8080 clean-architecture` +2. Run the Container: `docker run --name clean-architecture -d -p 80:80 -p 8080:8080 clean-architecture` ### Using docker-compose diff --git a/docker-compose.yml b/docker-compose.yml index 075b50f..b4c1a8a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3" services: app: build: @@ -37,7 +36,7 @@ services: - 1433:1433 redis: - image: docker.io/bitnami/redis:7.4 + image: redis:latest environment: # ALLOW_EMPTY_PASSWORD is recommended only for development. - ALLOW_EMPTY_PASSWORD=yes diff --git a/k8s-deployments/clean-architecture.yml b/k8s-deployments/clean-architecture.yml index dc48762..c65773e 100644 --- a/k8s-deployments/clean-architecture.yml +++ b/k8s-deployments/clean-architecture.yml @@ -42,7 +42,7 @@ spec: protocol: TCP env: - name: ASPNETCORE_HTTP_PORTS - value: 80 + value: "80" - name: Kestrel__Endpoints__Http__Url value: http://+:80 - name: Kestrel__Endpoints__Grpc__Url diff --git a/k8s-deployments/redis.yml b/k8s-deployments/redis.yml index 6196e1a..4824019 100644 --- a/k8s-deployments/redis.yml +++ b/k8s-deployments/redis.yml @@ -27,7 +27,7 @@ spec: spec: containers: - name: redis - image: docker.io/bitnami/redis:7.4 + image: redis:latest env: # ALLOW_EMPTY_PASSWORD is recommended only for development. - name: ALLOW_EMPTY_PASSWORD From 5a92022fdc59c85e1cdf2237c73e0f436c32bad0 Mon Sep 17 00:00:00 2001 From: alex289 Date: Fri, 22 Nov 2024 16:00:05 +0100 Subject: [PATCH 10/11] feat: Add redis test --- .../ExternalServices/RedisTestFixture.cs | 34 +++++++++++++++++++ .../ExternalServices/RedisTests.cs | 34 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 CleanArchitecture.IntegrationTests/ExternalServices/RedisTestFixture.cs create mode 100644 CleanArchitecture.IntegrationTests/ExternalServices/RedisTests.cs diff --git a/CleanArchitecture.IntegrationTests/ExternalServices/RedisTestFixture.cs b/CleanArchitecture.IntegrationTests/ExternalServices/RedisTestFixture.cs new file mode 100644 index 0000000..17b429c --- /dev/null +++ b/CleanArchitecture.IntegrationTests/ExternalServices/RedisTestFixture.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; +using CleanArchitecture.Domain.Entities; +using CleanArchitecture.Infrastructure.Database; +using CleanArchitecture.IntegrationTests.Fixtures; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.DependencyInjection; + +namespace CleanArchitecture.IntegrationTests.ExternalServices; + +public sealed class RedisTestFixture : TestFixtureBase +{ + public Guid CreatedTenantId { get; } = Guid.NewGuid(); + + public IDistributedCache DistributedCache { get; } + + public RedisTestFixture() + { + DistributedCache = Factory.Services.GetRequiredService(); + } + + public async Task SeedTestData() + { + await GlobalSetupFixture.RespawnDatabaseAsync(); + + using var context = Factory.Services.GetRequiredService(); + + context.Tenants.Add(new Tenant( + CreatedTenantId, + "Test Tenant")); + + await context.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/ExternalServices/RedisTests.cs b/CleanArchitecture.IntegrationTests/ExternalServices/RedisTests.cs new file mode 100644 index 0000000..8ead306 --- /dev/null +++ b/CleanArchitecture.IntegrationTests/ExternalServices/RedisTests.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using CleanArchitecture.Application.ViewModels.Tenants; +using CleanArchitecture.Domain; +using CleanArchitecture.Domain.Entities; +using CleanArchitecture.IntegrationTests.Extensions; +using FluentAssertions; +using Microsoft.Extensions.Caching.Distributed; +using Newtonsoft.Json; + +namespace CleanArchitecture.IntegrationTests.ExternalServices; + +public sealed class RedisTests +{ + private readonly RedisTestFixture _fixture = new(); + + [OneTimeSetUp] + public async Task Setup() => await _fixture.SeedTestData(); + + [Test, Order(0)] + public async Task Should_Get_Tenant_By_Id_And_Ensure_Cache() + { + var response = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}"); + var message = await response.Content.ReadAsJsonAsync(); + message!.Data!.Id.Should().Be(_fixture.CreatedTenantId); + + var json = await _fixture.DistributedCache.GetStringAsync(CacheKeyGenerator.GetEntityCacheKey(_fixture.CreatedTenantId)); + json.Should().NotBeNullOrEmpty(); + + var tenant = JsonConvert.DeserializeObject(json!)!; + + tenant.Should().NotBeNull(); + tenant.Id.Should().Be(_fixture.CreatedTenantId); + } +} \ No newline at end of file From f909b6175fd7b58da58c89cc57b40db02ae1f8d4 Mon Sep 17 00:00:00 2001 From: alex289 Date: Fri, 22 Nov 2024 16:07:34 +0100 Subject: [PATCH 11/11] feat: Aspire improvements --- CleanArchitecture.AppHost/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CleanArchitecture.AppHost/Program.cs b/CleanArchitecture.AppHost/Program.cs index 4780d21..81aba7e 100644 --- a/CleanArchitecture.AppHost/Program.cs +++ b/CleanArchitecture.AppHost/Program.cs @@ -1,6 +1,6 @@ var builder = DistributedApplication.CreateBuilder(args); -var redis = builder.AddRedis("Redis"); +var redis = builder.AddRedis("Redis").WithRedisInsight(); var rabbitPasswordRessource = new ParameterResource("password", _ => "guest"); var rabbitPasswordParameter = @@ -15,6 +15,7 @@ builder.AddProject("CleanArchitecture-Api") .WithOtlpExporter() + .WithHttpHealthCheck("/health") .WithReference(redis) .WaitFor(redis) .WithReference(rabbitMq)