From ab512476d03659cdc72fff9764ed19f1b05f2423 Mon Sep 17 00:00:00 2001 From: Meysam Hadeli Date: Tue, 22 Jul 2025 01:32:05 +0330 Subject: [PATCH] feat: add .net aspire integrations --- booking-microservices.sln | 21 ++ .../docker-compose.infrastructure.yaml | 3 +- .../docker-compose/docker-compose.yaml | 8 +- src/ApiGateway/src/Program.cs | 4 - src/Aspire/src/AppHost/AppHost.csproj | 32 +++ src/Aspire/src/AppHost/Program.cs | 216 ++++++++++++++++++ .../AppHost/Properties/launchSettings.json | 30 +++ .../src/AppHost/appsettings.Development.json | 8 + src/Aspire/src/AppHost/appsettings.json | 9 + src/BuildingBlocks/BuildingBlocks.csproj | 33 ++- src/BuildingBlocks/EFCore/Extensions.cs | 8 +- src/BuildingBlocks/EventStoreDB/Config.cs | 5 +- .../Events/IAggregateEventSourcing.cs | 2 - src/BuildingBlocks/HealthCheck/Extensions.cs | 103 +++++---- src/BuildingBlocks/Jwt/JwtExtensions.cs | 2 +- src/BuildingBlocks/Logging/Extensions.cs | 56 ----- src/BuildingBlocks/Logging/FileOptions.cs | 8 - src/BuildingBlocks/Logging/LogEnrichHelper.cs | 39 ---- src/BuildingBlocks/Logging/LogOptions.cs | 9 - src/BuildingBlocks/MassTransit/Extensions.cs | 35 ++- src/BuildingBlocks/Mongo/Extensions.cs | 9 +- .../OpenTelemetryCollector/Extensions.cs | 11 +- .../PersistMessageProcessor/Extensions.cs | 26 ++- .../Polly/CircuitBreakerOptions.cs | 7 - src/BuildingBlocks/Polly/Extensions.cs | 13 +- .../Polly/GrpcCircuitBreaker.cs | 46 ---- src/BuildingBlocks/Polly/GrpcRetry.cs | 43 ---- .../Polly/HttpClientCircuitBreaker.cs | 48 ---- src/BuildingBlocks/Polly/HttpClientRetry.cs | 44 ---- src/BuildingBlocks/Polly/PolicyOptions.cs | 7 - src/BuildingBlocks/Polly/RetryOptions.cs | 7 - src/BuildingBlocks/TestBase/TestBase.cs | 18 +- .../Booking/src/Booking.Api/Program.cs | 10 - .../src/Booking.Api/appsettings.docker.json | 76 +++--- .../Booking/src/Booking.Api/appsettings.json | 18 +- .../src/Booking.Api/appsettings.test.json | 43 ++-- .../Infrastructure/GrpcClientExtensions.cs | 63 ++++- .../InfrastructureExtensions.cs | 51 ++--- src/Services/Flight/src/Flight.Api/Program.cs | 10 - .../src/Flight.Api/appsettings.docker.json | 64 +++--- .../Flight/src/Flight.Api/appsettings.json | 10 +- .../src/Flight.Api/appsettings.test.json | 41 ++-- .../InfrastructureExtensions.cs | 55 +++-- .../Identity/src/Identity.Api/Program.cs | 11 - .../src/Identity.Api/appsettings.docker.json | 54 ++--- .../src/Identity.Api/appsettings.json | 20 +- .../src/Identity.Api/appsettings.test.json | 47 ++-- ...-key-E1668D5B7CCDD18C610506FCA7C5D194.json | 1 + .../InfrastructureExtensions.cs | 55 +++-- .../Identity/Features/RegisterNewUserTests.cs | 1 - .../Passenger/src/Passenger.Api/Program.cs | 10 - .../src/Passenger.Api/appsettings.docker.json | 64 +++--- .../src/Passenger.Api/appsettings.json | 20 +- .../src/Passenger.Api/appsettings.test.json | 41 ++-- .../InfrastructureExtensions.cs | 59 +++-- 55 files changed, 860 insertions(+), 874 deletions(-) create mode 100644 src/Aspire/src/AppHost/AppHost.csproj create mode 100644 src/Aspire/src/AppHost/Program.cs create mode 100644 src/Aspire/src/AppHost/Properties/launchSettings.json create mode 100644 src/Aspire/src/AppHost/appsettings.Development.json create mode 100644 src/Aspire/src/AppHost/appsettings.json delete mode 100644 src/BuildingBlocks/Logging/Extensions.cs delete mode 100644 src/BuildingBlocks/Logging/FileOptions.cs delete mode 100644 src/BuildingBlocks/Logging/LogEnrichHelper.cs delete mode 100644 src/BuildingBlocks/Logging/LogOptions.cs delete mode 100644 src/BuildingBlocks/Polly/CircuitBreakerOptions.cs delete mode 100644 src/BuildingBlocks/Polly/GrpcCircuitBreaker.cs delete mode 100644 src/BuildingBlocks/Polly/GrpcRetry.cs delete mode 100644 src/BuildingBlocks/Polly/HttpClientCircuitBreaker.cs delete mode 100644 src/BuildingBlocks/Polly/HttpClientRetry.cs delete mode 100644 src/BuildingBlocks/Polly/PolicyOptions.cs delete mode 100644 src/BuildingBlocks/Polly/RetryOptions.cs create mode 100644 src/Services/Identity/src/Identity.Api/keys/is-signing-key-E1668D5B7CCDD18C610506FCA7C5D194.json diff --git a/booking-microservices.sln b/booking-microservices.sln index f189a38..90aea0d 100644 --- a/booking-microservices.sln +++ b/booking-microservices.sln @@ -69,6 +69,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Test", "src\Ser EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Test", "src\Services\Passenger\tests\IntegrationTest\Integration.Test.csproj", "{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aspire", "Aspire", "{D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C4287034-6833-4505-A6EB-704A86392ECB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "src\Aspire\src\AppHost\AppHost.csproj", "{490BCB11-314C-473C-9B85-A32164783507}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -271,6 +277,18 @@ Global {A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Release|x64.Build.0 = Release|Any CPU {A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Release|x86.ActiveCfg = Release|Any CPU {A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Release|x86.Build.0 = Release|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Debug|Any CPU.Build.0 = Debug|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Debug|x64.ActiveCfg = Debug|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Debug|x64.Build.0 = Debug|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Debug|x86.ActiveCfg = Debug|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Debug|x86.Build.0 = Debug|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Release|Any CPU.ActiveCfg = Release|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Release|Any CPU.Build.0 = Release|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Release|x64.ActiveCfg = Release|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Release|x64.Build.0 = Release|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Release|x86.ActiveCfg = Release|Any CPU + {490BCB11-314C-473C-9B85-A32164783507}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -308,5 +326,8 @@ Global {B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB} = {51D8F471-B8EB-AD1C-0E89-AA84C5D0C759} {0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01} = {4B043475-1AFA-C467-FE09-A46D09CD6936} {A85AE27D-81ED-485A-BA4B-161B25BEB8A5} = {54BCCDE8-25E6-6FCB-4A9E-D5D2AF76D352} + {D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA} = {CD4A4407-C3B0-422D-BB8C-2A810CED9938} + {C4287034-6833-4505-A6EB-704A86392ECB} = {D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA} + {490BCB11-314C-473C-9B85-A32164783507} = {C4287034-6833-4505-A6EB-704A86392ECB} EndGlobalSection EndGlobal diff --git a/deployments/docker-compose/docker-compose.infrastructure.yaml b/deployments/docker-compose/docker-compose.infrastructure.yaml index 0c5f610..9fe7d9b 100644 --- a/deployments/docker-compose/docker-compose.infrastructure.yaml +++ b/deployments/docker-compose/docker-compose.infrastructure.yaml @@ -358,4 +358,5 @@ networks: driver: bridge volumes: - postgres-data: + elastic-data: + postgres-data: \ No newline at end of file diff --git a/deployments/docker-compose/docker-compose.yaml b/deployments/docker-compose/docker-compose.yaml index 1e35c38..99e8c9b 100644 --- a/deployments/docker-compose/docker-compose.yaml +++ b/deployments/docker-compose/docker-compose.yaml @@ -347,16 +347,16 @@ services: ###################################################### - # Gateway + # Api-Gateway ###################################################### - gateway: - image: gateway + api-gateway: + image: api-gateway build: args: Version: "1" context: ../../ dockerfile: src/ApiGateway/dev.Dockerfile - container_name: gateway + container_name: api-gateway ports: - "5001:80" - "5000:443" diff --git a/src/ApiGateway/src/Program.cs b/src/ApiGateway/src/Program.cs index 96cba15..c2a3738 100644 --- a/src/ApiGateway/src/Program.cs +++ b/src/ApiGateway/src/Program.cs @@ -1,7 +1,5 @@ -using BuildingBlocks.Logging; using BuildingBlocks.Web; using Figgle; -using Serilog; var builder = WebApplication.CreateBuilder(args); var env = builder.Environment; @@ -10,7 +8,6 @@ Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name)); -builder.AddCustomSerilog(env); builder.Services.AddControllers(); builder.Services.AddHttpContextAccessor(); @@ -18,7 +15,6 @@ builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSecti var app = builder.Build(); -app.UseSerilogRequestLogging(); app.UseCorrelationId(); app.UseRouting(); app.UseHttpsRedirection(); diff --git a/src/Aspire/src/AppHost/AppHost.csproj b/src/Aspire/src/AppHost/AppHost.csproj new file mode 100644 index 0000000..b78560f --- /dev/null +++ b/src/Aspire/src/AppHost/AppHost.csproj @@ -0,0 +1,32 @@ + + + + + + Exe + net9.0 + enable + enable + true + bde28db3-85ba-4201-b889-0f3faba24169 + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Aspire/src/AppHost/Program.cs b/src/Aspire/src/AppHost/Program.cs new file mode 100644 index 0000000..a23482b --- /dev/null +++ b/src/Aspire/src/AppHost/Program.cs @@ -0,0 +1,216 @@ +using System.Net.Sockets; + +var builder = DistributedApplication.CreateBuilder(args); + +// 1. Database Services +var username = builder.AddParameter("username", "postgres", secret: true); +var password = builder.AddParameter("password", "postgres", secret: true); + +var postgres = builder.AddPostgres("postgres", username, password) + .WithImage("postgres:latest") + .WithEndpoint(port: 5432, targetPort: 5432, name: "postgres") + .WithArgs( + "-c", "wal_level=logical", + "-c", "max_prepared_transactions=10" + ) + .WithDataVolume("postgres-data") + .WithLifetime(ContainerLifetime.Persistent); + +var flightDb = postgres.AddDatabase("flight"); +var passengerDb = postgres.AddDatabase("passenger"); +var identityDb = postgres.AddDatabase("identity"); +var persistMessageDb = postgres.AddDatabase("persist-message"); + +var mongoUsername = builder.AddParameter("mongo-username", "root"); +var mongoPassword = builder.AddParameter("mongo-password", "secret", secret: true); + +var mongo = builder.AddMongoDB("mongo", userName: mongoUsername, password: mongoPassword) + .WithImage("mongo:latest") + .WithEndpoint(port: 27017, targetPort: 27017, name: "mongo") + .WithDataVolume("mongo-data") + .WithLifetime(ContainerLifetime.Persistent); + +var redis = builder.AddRedis("redis") + .WithImage("redis:latest") + .WithEndpoint(port: 6379, targetPort: 6379, name: "redis") + .WithDataVolume("redis-data") + .WithLifetime(ContainerLifetime.Persistent); + +var eventstore = builder.AddEventStore("eventstore") + .WithImage("eventstore/eventstore") + .WithEnvironment("EVENTSTORE_CLUSTER_SIZE", "1") + .WithEnvironment("EVENTSTORE_RUN_PROJECTIONS", "All") + .WithEnvironment("EVENTSTORE_START_STANDARD_PROJECTIONS", "True") + .WithEnvironment("EVENTSTORE_INSECURE", "True") + .WithEnvironment("EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "True") + .WithHttpEndpoint(port: 2113, targetPort: 2113, name: "eventstore-http") + .WithDataVolume("eventstore-data") + .WithLifetime(ContainerLifetime.Persistent); + +// 2. Messaging Services +var rabbitmq = builder.AddRabbitMQ("rabbitmq") + .WithImage("rabbitmq:management") + .WithEndpoint(port: 5672, targetPort: 5672, name: "rabbitmq-amqp") + .WithEndpoint(port: 15672, targetPort: 15672, name: "rabbitmq-management") + .WithLifetime(ContainerLifetime.Persistent); + +// 3. Observability Services +var jaeger = builder.AddContainer("jaeger-all-in-one", "jaegertracing/all-in-one") + .WithEndpoint(port: 6831, targetPort: 6831, name: "jaeger-udp", protocol: ProtocolType.Udp) + .WithEndpoint(port: 16686, targetPort: 16686, name: "jaeger-ui") + .WithEndpoint(port: 14268, targetPort: 14268, name: "jaeger-api") + .WithEndpoint(port: 14317, targetPort: 4317, name: "jaeger-otlp-grpc") + .WithEndpoint(port: 14318, targetPort: 4318, name: "jaeger-otlp-http") + .WithLifetime(ContainerLifetime.Persistent); + +var zipkin = builder.AddContainer("zipkin-all-in-one", "openzipkin/zipkin") + .WithEndpoint(port: 9411, targetPort: 9411, name: "zipkin-api") + .WithLifetime(ContainerLifetime.Persistent); + +var otelCollector = builder.AddContainer("otel-collector", "otel/opentelemetry-collector-contrib") + .WithBindMount("../../../../deployments/configs/otel-collector-config.yaml", "/etc/otelcol-contrib/config.yaml", isReadOnly: true) + .WithArgs("--config=/etc/otelcol-contrib/config.yaml") + .WithEndpoint(port: 11888, targetPort: 1888, name: "otel-pprof") + .WithEndpoint(port: 8888, targetPort: 8888, name: "otel-metrics") + .WithEndpoint(port: 8889, targetPort: 8889, name: "otel-exporter-metrics") + .WithEndpoint(port: 13133, targetPort: 13133, name: "otel-health") + .WithEndpoint(port: 4317, targetPort: 4317, name: "otel-grpc") + .WithEndpoint(port: 4318, targetPort: 4318, name: "otel-http") + .WithEndpoint(port: 55679, targetPort: 55679, name: "otel-zpages") + .WithLifetime(ContainerLifetime.Persistent); + +var prometheus = builder.AddContainer("prometheus", "prom/prometheus") + .WithBindMount("../../../../deployments/configs/prometheus.yaml", "/etc/prometheus/prometheus.yml") + .WithArgs( + "--config.file=/etc/prometheus/prometheus.yml", + "--storage.tsdb.path=/prometheus", + "--web.console.libraries=/usr/share/prometheus/console_libraries", + "--web.console.templates=/usr/share/prometheus/consoles", + "--web.enable-remote-write-receiver") + .WithEndpoint(port: 9090, targetPort: 9090, name: "prometheus-web") + .WithLifetime(ContainerLifetime.Persistent); + +var grafana = builder.AddContainer("grafana", "grafana/grafana") + .WithEnvironment("GF_INSTALL_PLUGINS", "grafana-clock-panel,grafana-simple-json-datasource") + .WithEnvironment("GF_SECURITY_ADMIN_USER", "admin") + .WithEnvironment("GF_SECURITY_ADMIN_PASSWORD", "admin") + .WithEnvironment("GF_FEATURE_TOGGLES_ENABLE", "traceqlEditor") + .WithBindMount("../../../../deployments/configs/grafana/provisioning", "/etc/grafana/provisioning") + .WithBindMount("../../../../deployments/configs/grafana/dashboards", "/var/lib/grafana/dashboards") + .WithEndpoint(port: 3000, targetPort: 3000, name: "grafana-web") + .WithLifetime(ContainerLifetime.Persistent); + +var nodeExporter = builder.AddContainer("node-exporter", "prom/node-exporter") + .WithBindMount("/proc", "/host/proc", isReadOnly: true) + .WithBindMount("/sys", "/host/sys", isReadOnly: true) + .WithBindMount("/", "/rootfs", isReadOnly: true) + .WithArgs( + "--path.procfs=/host/proc", + "--path.rootfs=/rootfs", + "--path.sysfs=/host/sys") + .WithEndpoint(port: 9101, targetPort: 9100, name: "node-exporter") + .WithLifetime(ContainerLifetime.Persistent); + +var tempo = builder.AddContainer("tempo", "grafana/tempo") + .WithBindMount("../../../../deployments/configs/tempo.yaml", "/etc/tempo.yaml", isReadOnly: true) + .WithArgs("--config.file=/etc/tempo.yaml") + .WithEndpoint(port: 3200, targetPort: 3200, name: "tempo") + .WithEndpoint(port: 24317, targetPort: 4317, name: "tempo-otlp-grpc") + .WithEndpoint(port: 24318, targetPort: 4318, name: "tempo-otlp-http") + .WithLifetime(ContainerLifetime.Persistent); + +var loki = builder.AddContainer("loki", "grafana/loki") + .WithBindMount("../../../../deployments/configs/loki-config.yaml", "/etc/loki/local-config.yaml", isReadOnly: true) + .WithArgs("-config.file=/etc/loki/local-config.yaml") + .WithEndpoint(port: 3100, targetPort: 3100, name: "loki") + .WithLifetime(ContainerLifetime.Persistent); + +var elasticsearch = builder.AddElasticsearch("elasticsearch") + .WithImage("docker.elastic.co/elasticsearch/elasticsearch:8.17.0") + .WithEnvironment("discovery.type", "single-node") + .WithEnvironment("cluster.name", "docker-cluster") + .WithEnvironment("node.name", "docker-node") + .WithEnvironment("ES_JAVA_OPTS", "-Xms512m -Xmx512m") + .WithEnvironment("xpack.security.enabled", "false") + .WithEnvironment("xpack.security.http.ssl.enabled", "false") + .WithEnvironment("xpack.security.transport.ssl.enabled", "false") + .WithEnvironment("network.host", "0.0.0.0") + .WithEnvironment("http.port", "9200") + .WithEnvironment("transport.host", "localhost") + .WithEnvironment("bootstrap.memory_lock", "true") + .WithEnvironment("cluster.routing.allocation.disk.threshold_enabled", "false") + .WithEndpoint(port: 9200, targetPort: 9200, name: "elasticsearch-http") + .WithEndpoint(port: 9300, targetPort: 9300, name: "elasticsearch-transport") + .WithDataVolume("elastic-data") + .WithLifetime(ContainerLifetime.Persistent); + +var kibana = builder.AddContainer("kibana", "docker.elastic.co/kibana/kibana:8.17.0") + .WithEnvironment("ELASTICSEARCH_HOSTS", "http://elasticsearch:9200") + .WithEndpoint(port: 5601, targetPort: 5601, name: "kibana") + .WithReference(elasticsearch) + .WaitFor(elasticsearch) + .WithLifetime(ContainerLifetime.Persistent); + + +// 5. Application Services +var identity = builder.AddProject("identity-service") + .WithReference(persistMessageDb) + .WaitFor(persistMessageDb) + .WithReference(identityDb) + .WaitFor(identityDb) + .WithReference(mongo) + .WaitFor(mongo) + .WithReference(rabbitmq) + .WaitFor(rabbitmq) + .WithHttpEndpoint(port: 6005, name: "identity-http") + .WithHttpsEndpoint(port: 5005, name: "identity-https"); + +var passenger = builder.AddProject("passenger-service") + .WithReference(persistMessageDb) + .WaitFor(persistMessageDb) + .WithReference(passengerDb) + .WaitFor(passengerDb) + .WithReference(mongo) + .WaitFor(mongo) + .WithReference(rabbitmq) + .WaitFor(rabbitmq) + .WithHttpEndpoint(port: 6012, name: "passenger-http") + .WithHttpsEndpoint(port: 5012, name: "passenger-https"); + +var flight = builder.AddProject("flight-service") + .WithReference(persistMessageDb) + .WaitFor(persistMessageDb) + .WithReference(flightDb) + .WaitFor(flightDb) + .WithReference(mongo) + .WaitFor(mongo) + .WithReference(rabbitmq) + .WaitFor(rabbitmq) + .WithHttpEndpoint(port: 5004, name: "flight-http") + .WithHttpsEndpoint(port: 5003, name: "flight-https"); + +var booking = builder.AddProject("booking-service") + .WithReference(persistMessageDb) + .WaitFor(persistMessageDb) + .WithReference(eventstore) + .WaitFor(eventstore) + .WithReference(mongo) + .WaitFor(mongo) + .WithReference(rabbitmq) + .WaitFor(rabbitmq) + .WithHttpEndpoint(port: 6010, name: "booking-http") + .WithHttpsEndpoint(port: 5010, name: "booking-https"); + +var gateway = builder.AddProject("api-gateway") + .WithReference(flight) + .WaitFor(flight) + .WithReference(passenger) + .WaitFor(passenger) + .WithReference(identity) + .WaitFor(identity) + .WithReference(booking) + .WaitFor(booking) + .WithHttpEndpoint(port: 5001, name: "gateway-http") + .WithHttpsEndpoint(port: 5000, name: "gateway-https"); + +builder.Build().Run(); diff --git a/src/Aspire/src/AppHost/Properties/launchSettings.json b/src/Aspire/src/AppHost/Properties/launchSettings.json new file mode 100644 index 0000000..2d6c02b --- /dev/null +++ b/src/Aspire/src/AppHost/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7000;http://localhost:7001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://otel-collector:4317", + "ASPIRE_DASHBOARD_OTLP_HTTP_ENDPOINT_URL": "http://otel-collector:4318" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:7001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://otel-collector:4317", + "ASPIRE_DASHBOARD_OTLP_HTTP_ENDPOINT_URL": "http://otel-collector:4318", + "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true" + } + } + } +} diff --git a/src/Aspire/src/AppHost/appsettings.Development.json b/src/Aspire/src/AppHost/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/Aspire/src/AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Aspire/src/AppHost/appsettings.json b/src/Aspire/src/AppHost/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/src/Aspire/src/AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/BuildingBlocks/BuildingBlocks.csproj b/src/BuildingBlocks/BuildingBlocks.csproj index 7f8b487..97293ac 100644 --- a/src/BuildingBlocks/BuildingBlocks.csproj +++ b/src/BuildingBlocks/BuildingBlocks.csproj @@ -6,16 +6,6 @@ - - - - - - - - - - @@ -27,6 +17,8 @@ + + @@ -50,16 +42,6 @@ - - - - - - - - - - @@ -79,7 +61,18 @@ + + + + + + + + + + + diff --git a/src/BuildingBlocks/EFCore/Extensions.cs b/src/BuildingBlocks/EFCore/Extensions.cs index 60442ad..d602f32 100644 --- a/src/BuildingBlocks/EFCore/Extensions.cs +++ b/src/BuildingBlocks/EFCore/Extensions.cs @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -14,7 +15,7 @@ namespace BuildingBlocks.EFCore; public static class Extensions { - public static IServiceCollection AddCustomDbContext(this WebApplicationBuilder builder, string connectionName = "") + public static IServiceCollection AddCustomDbContext(this WebApplicationBuilder builder, string? connectionName = "") where TContext : DbContext, IDbContext { AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); @@ -24,9 +25,8 @@ public static class Extensions builder.Services.AddDbContext( (sp, options) => { - string? connectionString = string.IsNullOrEmpty(connectionName) ? - sp.GetRequiredService().ConnectionString : - builder.Configuration?.GetSection("PostgresOptions:ConnectionString")[connectionName]; + var aspireConnectionString = builder.Configuration.GetConnectionString(connectionName.Kebaberize()); + var connectionString = aspireConnectionString ?? sp.GetRequiredService().ConnectionString; ArgumentException.ThrowIfNullOrEmpty(connectionString); diff --git a/src/BuildingBlocks/EventStoreDB/Config.cs b/src/BuildingBlocks/EventStoreDB/Config.cs index a9133ad..6a1a769 100644 --- a/src/BuildingBlocks/EventStoreDB/Config.cs +++ b/src/BuildingBlocks/EventStoreDB/Config.cs @@ -24,15 +24,16 @@ public record EventStoreDBOptions( public static class EventStoreDBConfigExtensions { - public static IServiceCollection AddEventStoreDB(this IServiceCollection services, IConfiguration config, + public static IServiceCollection AddEventStoreDB(this IServiceCollection services, IConfiguration configuration, EventStoreDBOptions? options = null) { services .AddSingleton(x => { + var aspireConnectionString = configuration.GetConnectionString("eventstore"); var eventStoreOptions = services.GetOptions(nameof(EventStoreOptions)); - return new EventStoreClient(EventStoreClientSettings.Create(eventStoreOptions.ConnectionString)); + return new EventStoreClient(EventStoreClientSettings.Create(aspireConnectionString ?? eventStoreOptions.ConnectionString)); }) .AddScoped(typeof(IEventStoreDBRepository<>), typeof(EventStoreDBRepository<>)) .AddTransient(); diff --git a/src/BuildingBlocks/EventStoreDB/Events/IAggregateEventSourcing.cs b/src/BuildingBlocks/EventStoreDB/Events/IAggregateEventSourcing.cs index 47a1af4..825fba2 100644 --- a/src/BuildingBlocks/EventStoreDB/Events/IAggregateEventSourcing.cs +++ b/src/BuildingBlocks/EventStoreDB/Events/IAggregateEventSourcing.cs @@ -3,8 +3,6 @@ using BuildingBlocks.Core.Model; namespace BuildingBlocks.EventStoreDB.Events { - using Microsoft.FSharp.Control; - public interface IAggregateEventSourcing : IProjection, IEntity { IReadOnlyList DomainEvents { get; } diff --git a/src/BuildingBlocks/HealthCheck/Extensions.cs b/src/BuildingBlocks/HealthCheck/Extensions.cs index 137ed4e..0c58f0a 100644 --- a/src/BuildingBlocks/HealthCheck/Extensions.cs +++ b/src/BuildingBlocks/HealthCheck/Extensions.cs @@ -1,49 +1,71 @@ using BuildingBlocks.EFCore; -using BuildingBlocks.Logging; +using BuildingBlocks.EventStoreDB; using BuildingBlocks.MassTransit; using BuildingBlocks.Mongo; -using BuildingBlocks.OpenTelemetryCollector; using BuildingBlocks.Web; -using HealthChecks.UI.Client; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using MongoDB.Driver; +using RabbitMQ.Client; namespace BuildingBlocks.HealthCheck; public static class Extensions { + private const string HealthEndpointPath = "/health"; + private const string AlivenessEndpointPath = "/alive"; + public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services) { var healthOptions = services.GetOptions(nameof(HealthOptions)); - if (!healthOptions.Enabled) - return services; - - var appOptions = services.GetOptions(nameof(AppOptions)); - var postgresOptions = services.GetOptions(nameof(PostgresOptions)); - var rabbitMqOptions = services.GetOptions(nameof(RabbitMqOptions)); - var mongoOptions = services.GetOptions(nameof(MongoOptions)); - - var healthChecksBuilder = services.AddHealthChecks() - .AddRabbitMQ( - rabbitConnectionString: - $"amqp://{rabbitMqOptions.UserName}:{rabbitMqOptions.Password}@{rabbitMqOptions.HostName}"); - - if (mongoOptions.ConnectionString is not null) - healthChecksBuilder.AddMongoDb(mongoOptions.ConnectionString); - - if (postgresOptions.ConnectionString is not null) - healthChecksBuilder.AddNpgSql(postgresOptions.ConnectionString); - - services.AddHealthChecksUI(setup => + if (healthOptions.Enabled) { - setup.SetEvaluationTimeInSeconds(60); // time in seconds between check - setup.AddHealthCheckEndpoint($"Basic Health Check - {appOptions.Name}", "/healthz"); - }).AddInMemoryStorage(); + var appOptions = services.GetOptions(nameof(AppOptions)); + var postgresOptions = services.GetOptions(nameof(PostgresOptions)); + var rabbitMqOptions = services.GetOptions(nameof(RabbitMqOptions)); + var eventStoreOptions = services.GetOptions(nameof(EventStoreOptions)); + var mongoOptions = services.GetOptions(nameof(MongoOptions)); + var healthChecksBuilder = services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]) + .AddRabbitMQ( + serviceProvider => + { + var factory = new ConnectionFactory + { + Uri = new Uri($"amqp://{rabbitMqOptions.UserName}:{rabbitMqOptions.Password}@{rabbitMqOptions.HostName}"), + }; + return factory.CreateConnectionAsync(); + }); + + if (!string.IsNullOrEmpty(mongoOptions.ConnectionString)) + { + healthChecksBuilder.AddMongoDb( + clientFactory: _ => new MongoClient(mongoOptions.ConnectionString), + name: "MongoDB-Health", + failureStatus: HealthStatus.Unhealthy, + timeout: TimeSpan.FromSeconds(10)); + } + + if (!string.IsNullOrEmpty(postgresOptions.ConnectionString)) + healthChecksBuilder.AddNpgSql(postgresOptions.ConnectionString); + + if (!string.IsNullOrEmpty(eventStoreOptions.ConnectionString)) + healthChecksBuilder.AddEventStore(eventStoreOptions.ConnectionString); + + services.AddHealthChecksUI(setup => + { + setup.SetEvaluationTimeInSeconds(60); // time in seconds between check + setup.AddHealthCheckEndpoint($"Self Check - {appOptions.Name}", HealthEndpointPath); + }).AddInMemoryStorage(); + } + + services.AddHealthChecks().AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); return services; } @@ -51,26 +73,17 @@ public static class Extensions { var healthOptions = app.Configuration.GetOptions(nameof(HealthOptions)); - if (!healthOptions.Enabled) - return app; - - app.UseHealthChecks("/healthz", - new HealthCheckOptions - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse, - ResultStatusCodes = - { - [HealthStatus.Healthy] = StatusCodes.Status200OK, - [HealthStatus.Degraded] = StatusCodes.Status500InternalServerError, - [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable - } - }) - .UseHealthChecksUI(options => + if (app.Environment.IsDevelopment()) + { + app.MapHealthChecks(HealthEndpointPath); + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions { - options.ApiPath = "/healthcheck"; - options.UIPath = "/healthcheck-ui"; + Predicate = r => r.Tags.Contains("live"), }); + } + + if (healthOptions.Enabled) + app.MapHealthChecksUI(options => options.UIPath = "/health-ui"); return app; } diff --git a/src/BuildingBlocks/Jwt/JwtExtensions.cs b/src/BuildingBlocks/Jwt/JwtExtensions.cs index 749c56b..18909b0 100644 --- a/src/BuildingBlocks/Jwt/JwtExtensions.cs +++ b/src/BuildingBlocks/Jwt/JwtExtensions.cs @@ -19,7 +19,7 @@ namespace BuildingBlocks.Jwt { options.Authority = jwtOptions.Authority; options.Audience = jwtOptions.Audience; - options.RequireHttpsMetadata = true; + options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters { diff --git a/src/BuildingBlocks/Logging/Extensions.cs b/src/BuildingBlocks/Logging/Extensions.cs deleted file mode 100644 index 1c765c0..0000000 --- a/src/BuildingBlocks/Logging/Extensions.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Globalization; -using System.Text; -using BuildingBlocks.Web; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Serilog; -using Serilog.Events; -using Serilog.Exceptions; -using Serilog.Sinks.SpectreConsole; - -namespace BuildingBlocks.Logging -{ - public static class Extensions - { - public static WebApplicationBuilder AddCustomSerilog(this WebApplicationBuilder builder, IWebHostEnvironment env) - { - builder.Host.UseSerilog((context, services, loggerConfiguration) => - { - var logOptions = context.Configuration.GetSection(nameof(LogOptions)).Get(); - - var logLevel = Enum.TryParse(logOptions.Level, true, out var level) - ? level - : LogEventLevel.Information; - - loggerConfiguration - .MinimumLevel.Is(logLevel) - .WriteTo.SpectreConsole(logOptions.LogTemplate, logLevel) - .MinimumLevel.Override("Microsoft", LogEventLevel.Information) - // Only show ef-core information in error level - .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Error) - // Filter out ASP.NET Core infrastructure logs that are Information and below - .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) - .Enrich.WithExceptionDetails() - .Enrich.FromLogContext() - .ReadFrom.Configuration(context.Configuration); - - if (logOptions.File is { Enabled: true }) - { - var root = env.ContentRootPath; - Directory.CreateDirectory(Path.Combine(root, "logs")); - - var path = string.IsNullOrWhiteSpace(logOptions.File.Path) ? "logs/.txt" : logOptions.File.Path; - if (!Enum.TryParse(logOptions.File.Interval, true, out var interval)) - { - interval = RollingInterval.Day; - } - - loggerConfiguration.WriteTo.File(path, rollingInterval: interval, encoding: Encoding.UTF8, outputTemplate: logOptions.LogTemplate); - } - }); - - return builder; - } - } -} diff --git a/src/BuildingBlocks/Logging/FileOptions.cs b/src/BuildingBlocks/Logging/FileOptions.cs deleted file mode 100644 index b3ad623..0000000 --- a/src/BuildingBlocks/Logging/FileOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BuildingBlocks.Logging; - -public class FileOptions -{ - public bool Enabled { get; set; } - public string Path { get; set; } - public string Interval { get; set; } -} diff --git a/src/BuildingBlocks/Logging/LogEnrichHelper.cs b/src/BuildingBlocks/Logging/LogEnrichHelper.cs deleted file mode 100644 index 185cf32..0000000 --- a/src/BuildingBlocks/Logging/LogEnrichHelper.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Http; -using Serilog; - -namespace BuildingBlocks.Logging; - -public static class LogEnrichHelper -{ - //ref: https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-logging-the-selected-endpoint-name-with-serilog/ - public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext) - { - var request = httpContext.Request; - - // Set all the common properties available for every request - diagnosticContext.Set("Host", request.Host); - diagnosticContext.Set("Protocol", request.Protocol); - diagnosticContext.Set("Scheme", request.Scheme); - - // Only set it if available. You're not sending sensitive data in a querystring right?! - if (request.QueryString.HasValue) - { - diagnosticContext.Set("QueryString", request.QueryString.Value); - } - - // Set the content-type of the Response at this point - diagnosticContext.Set("ContentType", httpContext.Response.ContentType); - - // Retrieve the IEndpointFeature selected for the request - var endpoint = httpContext.GetEndpoint(); - if (endpoint is object) // endpoint != null - { - diagnosticContext.Set("EndpointName", endpoint.DisplayName); - } - - diagnosticContext.Set("ClientIP", httpContext.Connection.RemoteIpAddress); - - diagnosticContext.Set("UserId", request.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)); - } -} diff --git a/src/BuildingBlocks/Logging/LogOptions.cs b/src/BuildingBlocks/Logging/LogOptions.cs deleted file mode 100644 index ee4f777..0000000 --- a/src/BuildingBlocks/Logging/LogOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace BuildingBlocks.Logging -{ - public class LogOptions - { - public string Level { get; set; } - public FileOptions File { get; set; } - public string LogTemplate { get; set; } - } -} diff --git a/src/BuildingBlocks/MassTransit/Extensions.cs b/src/BuildingBlocks/MassTransit/Extensions.cs index 90b5365..86028ff 100644 --- a/src/BuildingBlocks/MassTransit/Extensions.cs +++ b/src/BuildingBlocks/MassTransit/Extensions.cs @@ -2,6 +2,7 @@ using System.Reflection; using BuildingBlocks.Web; using MassTransit; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -58,18 +59,30 @@ public static class Extensions configure.UsingRabbitMq( (context, configurator) => { - var rabbitMqOptions = - services.GetOptions(nameof(RabbitMqOptions)); + var configuration = context.GetRequiredService(); - configurator.Host( - rabbitMqOptions?.HostName, - rabbitMqOptions?.Port ?? 5672, - "/", - h => - { - h.Username(rabbitMqOptions?.UserName); - h.Password(rabbitMqOptions?.Password); - }); + var aspireConnectionString = configuration.GetConnectionString("rabbitmq"); + + if (!string.IsNullOrEmpty(aspireConnectionString)) + { + configurator.Host(new Uri(aspireConnectionString)); + } + else + { + var rabbitMqOptions = services.GetOptions(nameof(RabbitMqOptions)); + + ArgumentNullException.ThrowIfNull(rabbitMqOptions); + + configurator.Host( + rabbitMqOptions?.HostName, + rabbitMqOptions?.Port ?? 5672, + "/", + h => + { + h.Username(rabbitMqOptions.UserName); + h.Password(rabbitMqOptions.Password); + }); + } configurator.ConfigureEndpoints(context); diff --git a/src/BuildingBlocks/Mongo/Extensions.cs b/src/BuildingBlocks/Mongo/Extensions.cs index 32d9f92..943fa97 100644 --- a/src/BuildingBlocks/Mongo/Extensions.cs +++ b/src/BuildingBlocks/Mongo/Extensions.cs @@ -20,7 +20,14 @@ namespace BuildingBlocks.Mongo where TContextService : IMongoDbContext where TContextImplementation : MongoDbContext, TContextService { - services.Configure(configuration.GetSection(nameof(MongoOptions))); + // Configure MongoOptions with Aspire-aware defaults + services.AddOptions() + .Bind(configuration.GetSection(nameof(MongoOptions))) + .PostConfigure(options => + { + var aspireConnectionString = configuration.GetConnectionString("mongo"); + options.ConnectionString = aspireConnectionString ?? options.ConnectionString; + }); if (configurator is { }) { diff --git a/src/BuildingBlocks/OpenTelemetryCollector/Extensions.cs b/src/BuildingBlocks/OpenTelemetryCollector/Extensions.cs index 04bf73b..61dd0ee 100644 --- a/src/BuildingBlocks/OpenTelemetryCollector/Extensions.cs +++ b/src/BuildingBlocks/OpenTelemetryCollector/Extensions.cs @@ -29,9 +29,11 @@ namespace BuildingBlocks.OpenTelemetryCollector; // https://blog.codingmilitia.com/2023/09/05/observing-dotnet-microservices-with-opentelemetry-logs-traces-metrics/ public static class Extensions { + private const string HealthEndpointPath = "/health"; + private const string AlivenessEndpointPath = "/alive"; + public static WebApplicationBuilder AddCustomObservability(this WebApplicationBuilder builder) { - Activity.DefaultIdFormat = ActivityIdFormat.W3C; builder.Services.AddSingleton(); builder.AddCoreDiagnostics(); @@ -147,6 +149,13 @@ public static class Extensions .AddAspNetCoreInstrumentation(options => { options.RecordException = true; + // Don't trace requests to the health endpoint to avoid filling the dashboard with noise + options.Filter = httpContext => + !(httpContext.Request.Path.StartsWithSegments( + HealthEndpointPath, StringComparison.OrdinalIgnoreCase) || + httpContext.Request.Path.StartsWithSegments( + AlivenessEndpointPath, StringComparison.OrdinalIgnoreCase + )); }) .AddGrpcClientInstrumentation() .AddHttpClientInstrumentation(instrumentationOptions => diff --git a/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs b/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs index ed62cbd..329fbcd 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs @@ -1,26 +1,32 @@ using BuildingBlocks.Web; -using Microsoft.AspNetCore.Hosting; +using Humanizer; +using Microsoft.AspNetCore.Builder; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace BuildingBlocks.PersistMessageProcessor; public static class Extensions { - public static IServiceCollection AddPersistMessageProcessor(this IServiceCollection services) + public static IServiceCollection AddPersistMessageProcessor(this WebApplicationBuilder builder, string? connectionName = "persist-message") { AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); - services.AddValidateOptions(); + builder.Services.AddValidateOptions(); - services.AddDbContext( + builder.Services.AddDbContext( (sp, options) => { - var persistMessageOptions = sp.GetRequiredService(); + var aspireConnectionString = builder.Configuration.GetConnectionString(connectionName.Kebaberize()); + + var connectionString = aspireConnectionString ?? sp.GetRequiredService().ConnectionString; + + ArgumentException.ThrowIfNullOrEmpty(connectionString); options.UseNpgsql( - persistMessageOptions.ConnectionString, + connectionString, dbOptions => { dbOptions.MigrationsAssembly( @@ -34,7 +40,7 @@ public static class Extensions w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); }); - services.AddScoped( + builder.Services.AddScoped( provider => { var persistMessageDbContext = @@ -46,10 +52,10 @@ public static class Extensions return persistMessageDbContext; }); - services.AddScoped(); + builder.Services.AddScoped(); - services.AddHostedService(); + builder.Services.AddHostedService(); - return services; + return builder.Services; } } diff --git a/src/BuildingBlocks/Polly/CircuitBreakerOptions.cs b/src/BuildingBlocks/Polly/CircuitBreakerOptions.cs deleted file mode 100644 index ae54e41..0000000 --- a/src/BuildingBlocks/Polly/CircuitBreakerOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BuildingBlocks.Polly; - -public class CircuitBreakerOptions -{ - public int RetryCount { get; set; } - public int BreakDuration { get; set; } -} diff --git a/src/BuildingBlocks/Polly/Extensions.cs b/src/BuildingBlocks/Polly/Extensions.cs index d6cf527..0ae82c5 100644 --- a/src/BuildingBlocks/Polly/Extensions.cs +++ b/src/BuildingBlocks/Polly/Extensions.cs @@ -1,20 +1,23 @@ +using Microsoft.Extensions.Logging; + namespace BuildingBlocks.Polly; using global::Polly; -using Serilog; using Exception = System.Exception; public static class Extensions { + public static ILogger Logger { get; set; } = null!; + public static T RetryOnFailure(this object retrySource, Func action, int retryCount = 3) { var retryPolicy = Policy .Handle() .Retry(retryCount, (exception, retryAttempt, context) => - { - Log.Information($"Retry attempt: {retryAttempt}"); - Log.Error($"Exception: {exception.Message}"); - }); + { + Logger.LogInformation($"Retry attempt: {retryAttempt}"); + Logger.LogError($"Exception: {exception.Message}"); + }); return retryPolicy.Execute(action); } diff --git a/src/BuildingBlocks/Polly/GrpcCircuitBreaker.cs b/src/BuildingBlocks/Polly/GrpcCircuitBreaker.cs deleted file mode 100644 index 98d8076..0000000 --- a/src/BuildingBlocks/Polly/GrpcCircuitBreaker.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace BuildingBlocks.Polly; - -using System.Net; -using Ardalis.GuardClauses; -using BuildingBlocks.Web; -using global::Polly; -using Grpc.Core; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -public static class GrpcCircuitBreaker -{ - //ref: https://anthonygiretti.com/2020/03/31/grpc-asp-net-core-3-1-resiliency-with-polly/ - public static IHttpClientBuilder AddGrpcCircuitBreakerPolicyHandler(this IHttpClientBuilder httpClientBuilder) - { - return httpClientBuilder.AddPolicyHandler((sp, _) => - { - var options = sp.GetRequiredService().GetOptions(nameof(PolicyOptions)); - - Guard.Against.Null(options, nameof(options)); - - var loggerFactory = sp.GetRequiredService(); - var logger = loggerFactory.CreateLogger("PollyGrpcCircuitBreakerPoliciesLogger"); - - return Policy.HandleResult(r => !r.IsSuccessStatusCode) - .CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking: options.CircuitBreaker.RetryCount, - durationOfBreak: TimeSpan.FromSeconds(options.CircuitBreaker.BreakDuration), - onBreak: (response, breakDuration) => - { - if (response?.Exception != null) - { - logger.LogError(response.Exception, - "Service shutdown during {BreakDuration} after {RetryCount} failed retries", - breakDuration, - options.CircuitBreaker.RetryCount); - } - }, - onReset: () => - { - logger.LogInformation("Service restarted"); - }); - }); - } -} diff --git a/src/BuildingBlocks/Polly/GrpcRetry.cs b/src/BuildingBlocks/Polly/GrpcRetry.cs deleted file mode 100644 index 4128c4a..0000000 --- a/src/BuildingBlocks/Polly/GrpcRetry.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace BuildingBlocks.Polly; - -using System.Net; -using Ardalis.GuardClauses; -using BuildingBlocks.Web; -using global::Polly; -using Grpc.Core; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -public static class GrpcRetry -{ - //ref: https://anthonygiretti.com/2020/03/31/grpc-asp-net-core-3-1-resiliency-with-polly/ - public static IHttpClientBuilder AddGrpcRetryPolicyHandler(this IHttpClientBuilder httpClientBuilder) - { - return httpClientBuilder.AddPolicyHandler((sp, _) => - { - var options = sp.GetRequiredService().GetOptions(nameof(PolicyOptions)); - - Guard.Against.Null(options, nameof(options)); - - return Policy - .HandleResult(r => !r.IsSuccessStatusCode) - .WaitAndRetryAsync(options.Retry.RetryCount, - retryAttempt => TimeSpan.FromSeconds(options.Retry.SleepDuration), - onRetry: (response, timeSpan, retryCount, context) => - { - if (response?.Exception != null) - { - var loggerFactory = sp.GetRequiredService(); - var logger = loggerFactory.CreateLogger("PollyGrpcRetryPoliciesLogger"); - - logger.LogError(response.Exception, - "Request failed with {StatusCode}. Waiting {TimeSpan} before next retry. Retry attempt {RetryCount}.", - response.Result.StatusCode, - timeSpan, - retryCount); - } - }); - }); - } -} diff --git a/src/BuildingBlocks/Polly/HttpClientCircuitBreaker.cs b/src/BuildingBlocks/Polly/HttpClientCircuitBreaker.cs deleted file mode 100644 index 24f1e21..0000000 --- a/src/BuildingBlocks/Polly/HttpClientCircuitBreaker.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace BuildingBlocks.Polly; - -using System.Net; -using Ardalis.GuardClauses; -using BuildingBlocks.Web; -using global::Polly; -using global::Polly.Extensions.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Exception = System.Exception; - -public static class HttpClientCircuitBreaker -{ - // ref: https://anthonygiretti.com/2019/03/26/best-practices-with-httpclient-and-retry-policies-with-polly-in-net-core-2-part-2/ - public static IHttpClientBuilder AddHttpClientCircuitBreakerPolicyHandler(this IHttpClientBuilder httpClientBuilder) - { - return httpClientBuilder.AddPolicyHandler((sp, _) => - { - var options = sp.GetRequiredService().GetOptions(nameof(PolicyOptions)); - - Guard.Against.Null(options, nameof(options)); - - var loggerFactory = sp.GetRequiredService(); - var logger = loggerFactory.CreateLogger("PollyHttpClientCircuitBreakerPoliciesLogger"); - - return HttpPolicyExtensions.HandleTransientHttpError() - .OrResult(msg => msg.StatusCode == HttpStatusCode.BadRequest) - .CircuitBreakerAsync( - handledEventsAllowedBeforeBreaking: options.CircuitBreaker.RetryCount, - durationOfBreak: TimeSpan.FromSeconds(options.CircuitBreaker.BreakDuration), - onBreak: (response, breakDuration) => - { - if (response?.Exception != null) - { - logger.LogError(response.Exception, - "Service shutdown during {BreakDuration} after {RetryCount} failed retries", - breakDuration, - options.CircuitBreaker.RetryCount); - } - }, - onReset: () => - { - logger.LogInformation("Service restarted"); - }); - }); - } -} diff --git a/src/BuildingBlocks/Polly/HttpClientRetry.cs b/src/BuildingBlocks/Polly/HttpClientRetry.cs deleted file mode 100644 index 3735d50..0000000 --- a/src/BuildingBlocks/Polly/HttpClientRetry.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace BuildingBlocks.Polly; - -using System.Net; -using Ardalis.GuardClauses; -using BuildingBlocks.Web; -using global::Polly; -using global::Polly.Extensions.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -public static class HttpClientRetry -{ - // ref: https://anthonygiretti.com/2019/03/26/best-practices-with-httpclient-and-retry-policies-with-polly-in-net-core-2-part-2/ - public static IHttpClientBuilder AddHttpClientRetryPolicyHandler(this IHttpClientBuilder httpClientBuilder) - { - return httpClientBuilder.AddPolicyHandler((sp, _) => - { - var options = sp.GetRequiredService().GetOptions(nameof(PolicyOptions)); - - Guard.Against.Null(options, nameof(options)); - - var loggerFactory = sp.GetRequiredService(); - var logger = loggerFactory.CreateLogger("PollyHttpClientRetryPoliciesLogger"); - - return HttpPolicyExtensions.HandleTransientHttpError() - .OrResult(msg => msg.StatusCode == HttpStatusCode.BadRequest) - .OrResult(msg => msg.StatusCode == HttpStatusCode.InternalServerError) - .WaitAndRetryAsync(retryCount: options.Retry.RetryCount, - sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(options.Retry.SleepDuration), - onRetry: (response, timeSpan, retryCount, context) => - { - if (response?.Exception != null) - { - logger.LogError(response.Exception, - "Request failed with {StatusCode}. Waiting {TimeSpan} before next retry. Retry attempt {RetryCount}.", - response.Result.StatusCode, - timeSpan, - retryCount); - } - }); - }); - } -} diff --git a/src/BuildingBlocks/Polly/PolicyOptions.cs b/src/BuildingBlocks/Polly/PolicyOptions.cs deleted file mode 100644 index a16be25..0000000 --- a/src/BuildingBlocks/Polly/PolicyOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BuildingBlocks.Polly; - -public class PolicyOptions -{ - public RetryOptions Retry { get; set; } - public CircuitBreakerOptions CircuitBreaker { get; set; } -} diff --git a/src/BuildingBlocks/Polly/RetryOptions.cs b/src/BuildingBlocks/Polly/RetryOptions.cs deleted file mode 100644 index 51c5f53..0000000 --- a/src/BuildingBlocks/Polly/RetryOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BuildingBlocks.Polly; - -public class RetryOptions -{ - public int RetryCount { get; set; } - public int SleepDuration { get; set; } -} diff --git a/src/BuildingBlocks/TestBase/TestBase.cs b/src/BuildingBlocks/TestBase/TestBase.cs index 1591802..bc48330 100644 --- a/src/BuildingBlocks/TestBase/TestBase.cs +++ b/src/BuildingBlocks/TestBase/TestBase.cs @@ -20,14 +20,13 @@ using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using MongoDB.Driver; using NSubstitute; using Respawn; -using Serilog; using WebMotions.Fake.Authentication.JwtBearer; using Xunit; using Xunit.Abstractions; -using ILogger = Serilog.ILogger; namespace BuildingBlocks.TestBase; @@ -154,14 +153,15 @@ where TEntryPoint : class // ref: https://github.com/trbenning/serilog-sinks-xunit public ILogger CreateLogger(ITestOutputHelper output) { - if (output != null) - { - return new LoggerConfiguration() - .WriteTo.TestOutput(output) - .CreateLogger(); - } + if (output == null) + return null; - return null; + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddXunit(output); + builder.SetMinimumLevel(LogLevel.Debug); + }); + return loggerFactory.CreateLogger("TestLogger"); } protected async Task ExecuteScopeAsync(Func action) diff --git a/src/Services/Booking/src/Booking.Api/Program.cs b/src/Services/Booking/src/Booking.Api/Program.cs index 550e52f..c8d5c9e 100644 --- a/src/Services/Booking/src/Booking.Api/Program.cs +++ b/src/Services/Booking/src/Booking.Api/Program.cs @@ -4,22 +4,12 @@ using BuildingBlocks.Web; var builder = WebApplication.CreateBuilder(args); -builder.Host.UseDefaultServiceProvider((context, options) => -{ - // Service provider validation - // ref: https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/ - options.ValidateScopes = context.HostingEnvironment.IsDevelopment() || context.HostingEnvironment.IsStaging() || context.HostingEnvironment.IsEnvironment("tests"); - options.ValidateOnBuild = true; -}); - builder.AddMinimalEndpoints(assemblies: typeof(BookingRoot).Assembly); builder.AddInfrastructure(); var app = builder.Build(); app.MapMinimalEndpoints(); -app.UseAuthentication(); -app.UseAuthorization(); app.UseInfrastructure(); app.Run(); diff --git a/src/Services/Booking/src/Booking.Api/appsettings.docker.json b/src/Services/Booking/src/Booking.Api/appsettings.docker.json index 0a41e30..49995a6 100644 --- a/src/Services/Booking/src/Booking.Api/appsettings.docker.json +++ b/src/Services/Booking/src/Booking.Api/appsettings.docker.json @@ -1,46 +1,36 @@ { - "App": "Booking-Service", - "Logging": { - "LogLevel": { - "Default": "Debug", - "Microsoft.AspNetCore": "Warning" - } - }, - "PersistMessageOptions": { - "Interval": 30, - "Enabled": true, - "ConnectionString": "Server=postgres;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true" - }, - "EventStoreOptions": { - "ConnectionString": "esdb://eventstore:2113?tls=false" - }, - "MongoOptions": { - "ConnectionString": "mongodb://mongo:27017", - "DatabaseName": "booking-db" - }, - "RabbitMqOptions": { - "HostName": "rabbitmq", - "ExchangeName": "booking", - "UserName": "guest", - "Password": "guest", - "Port": 5672 - }, - "Jwt": { - "Authority": "http://identity:80", - "Audience": "booking-api" - }, - "Grpc": { - "FlightAddress": "flight:5003", - "PassengerAddress": "passenger:5003" - }, - "LogOptions": { - "Level": "information", - "LogTemplate": "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", - "File": { - "Enabled": false, - "Path": "logs/logs.txt", - "Interval": "day" + "App": "Booking-Service", + "Logging": { + "LogLevel": { + "Default": "Information" + } }, - }, - "AllowedHosts": "*" + "PersistMessageOptions": { + "Interval": 30, + "Enabled": true, + "ConnectionString": "Server=postgres;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true" + }, + "EventStoreOptions": { + "ConnectionString": "esdb://eventstore:2113?tls=false" + }, + "MongoOptions": { + "ConnectionString": "mongodb://mongo:27017", + "DatabaseName": "booking-db" + }, + "RabbitMqOptions": { + "HostName": "rabbitmq", + "ExchangeName": "booking", + "UserName": "guest", + "Password": "guest", + "Port": 5672 + }, + "Jwt": { + "Authority": "http://identity:80", + "Audience": "booking-api" + }, + "Grpc": { + "FlightAddress": "flight:5003", + "PassengerAddress": "passenger:5003" + }, + "AllowedHosts": "*" } diff --git a/src/Services/Booking/src/Booking.Api/appsettings.json b/src/Services/Booking/src/Booking.Api/appsettings.json index 0f5a180..5e988b5 100644 --- a/src/Services/Booking/src/Booking.Api/appsettings.json +++ b/src/Services/Booking/src/Booking.Api/appsettings.json @@ -2,14 +2,10 @@ "AppOptions": { "Name": "Booking-Service" }, - "LogOptions": { - "Level": "information", - "LogTemplate": "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", - "File": { - "Enabled": false, - "Path": "logs/logs.txt", - "Interval": "day" - }, + "Logging": { + "LogLevel": { + "Default": "Information" + } }, "Jwt": { "Authority": "http://localhost:6005", @@ -43,14 +39,14 @@ "ConnectionString": "mongodb://localhost:27017", "DatabaseName": "booking-db" }, - "HealthOptions": { - "Enabled": false - }, "PersistMessageOptions": { "Interval": 30, "Enabled": true, "ConnectionString": "Server=localhost;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true" }, + "HealthOptions": { + "Enabled": false + }, "ObservabilityOptions": { "InstrumentationName": "booking_service", "OTLPOptions": { diff --git a/src/Services/Booking/src/Booking.Api/appsettings.test.json b/src/Services/Booking/src/Booking.Api/appsettings.test.json index 42d2109..e8baa58 100644 --- a/src/Services/Booking/src/Booking.Api/appsettings.test.json +++ b/src/Services/Booking/src/Booking.Api/appsettings.test.json @@ -1,26 +1,23 @@ { - "RabbitMqOptions": { - "HostName": "localhost", - "ExchangeName": "booking", - "UserName": "guest", - "Password": "guest", - "Port": 5672 - }, - "Logging": { - "LogLevel": { - "Default": "Debug", - "Microsoft": "Debug", - "Microsoft.Hosting.Lifetime": "Debug", - "Microsoft.EntityFrameworkCore.Database.Command": "Debug" + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, + "RabbitMqOptions": { + "HostName": "localhost", + "ExchangeName": "booking", + "UserName": "guest", + "Password": "guest", + "Port": 5672 + }, + "MongoOptions": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "booking-db-test" + }, + "PersistMessageOptions": { + "Interval": 30, + "Enabled": true, + "ConnectionString": "Server=localhost;Port=5432;Database=persist_message_test;User Id=postgres;Password=postgres;Include Error Detail=true" } - }, - "MongoOptions": { - "ConnectionString": "mongodb://localhost:27017", - "DatabaseName": "booking-db-test" - }, - "PersistMessageOptions": { - "Interval": 30, - "Enabled": true, - "ConnectionString": "Server=localhost;Port=5432;Database=persist_message_test;User Id=postgres;Password=postgres;Include Error Detail=true" - } } diff --git a/src/Services/Booking/src/Booking/Extensions/Infrastructure/GrpcClientExtensions.cs b/src/Services/Booking/src/Booking/Extensions/Infrastructure/GrpcClientExtensions.cs index 8c6d501..6d5e19f 100644 --- a/src/Services/Booking/src/Booking/Extensions/Infrastructure/GrpcClientExtensions.cs +++ b/src/Services/Booking/src/Booking/Extensions/Infrastructure/GrpcClientExtensions.cs @@ -2,31 +2,76 @@ using Booking.Configuration; using BuildingBlocks.Web; using Flight; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http.Resilience; using Passenger; +using Polly; namespace Booking.Extensions.Infrastructure; -using BuildingBlocks.Polly; - public static class GrpcClientExtensions { public static IServiceCollection AddGrpcClients(this IServiceCollection services) { var grpcOptions = services.GetOptions("Grpc"); + var resilienceOptions = services.GetOptions(nameof(HttpStandardResilienceOptions)); services.AddGrpcClient(o => - { - o.Address = new Uri(grpcOptions.FlightAddress); - }) - .AddGrpcRetryPolicyHandler() - .AddGrpcCircuitBreakerPolicyHandler(); + { + o.Address = new Uri(grpcOptions.FlightAddress); + }) + .AddResilienceHandler( + "grpc-flight-resilience", + options => + { + var timeSpan = TimeSpan.FromMinutes(1); + + options.AddRetry( + new HttpRetryStrategyOptions + { + MaxRetryAttempts = 3, + }); + + options.AddCircuitBreaker( + new HttpCircuitBreakerStrategyOptions + { + SamplingDuration = timeSpan * 2, + }); + + options.AddTimeout( + new HttpTimeoutStrategyOptions + { + Timeout = timeSpan * 3, + }); + }); services.AddGrpcClient(o => { o.Address = new Uri(grpcOptions.PassengerAddress); }) - .AddGrpcRetryPolicyHandler() - .AddGrpcCircuitBreakerPolicyHandler(); + .AddResilienceHandler( + "grpc-passenger-resilience", + options => + { + var timeSpan = TimeSpan.FromMinutes(1); + + options.AddRetry( + new HttpRetryStrategyOptions + { + MaxRetryAttempts = 3, + }); + + options.AddCircuitBreaker( + new HttpCircuitBreakerStrategyOptions + { + SamplingDuration = timeSpan * 2, + }); + + options.AddTimeout( + new HttpTimeoutStrategyOptions + { + Timeout = timeSpan * 3, + }); + }); return services; } diff --git a/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs index 7ac8c25..791e5ad 100644 --- a/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -1,10 +1,8 @@ -using System.Threading.RateLimiting; using Booking.Data; using BuildingBlocks.Core; using BuildingBlocks.EventStoreDB; using BuildingBlocks.HealthCheck; using BuildingBlocks.Jwt; -using BuildingBlocks.Logging; using BuildingBlocks.Mapster; using BuildingBlocks.MassTransit; using BuildingBlocks.Mongo; @@ -20,7 +18,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Serilog; namespace Booking.Extensions.Infrastructure; @@ -31,6 +28,26 @@ public static class InfrastructureExtensions var configuration = builder.Configuration; var env = builder.Environment; + builder.Services.AddCustomHealthCheck(); + + builder.AddCustomObservability(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + http.AddStandardResilienceHandler(options => + { + var timeSpan = TimeSpan.FromMinutes(1); + options.CircuitBreaker.SamplingDuration = timeSpan * 2; + options.TotalRequestTimeout.Timeout = timeSpan * 3; + options.Retry.MaxRetryAttempts = 3; + }); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -44,25 +61,10 @@ public static class InfrastructureExtensions Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name)); - builder.Services.AddRateLimiter(options => - { - options.GlobalLimiter = PartitionedRateLimiter.Create(httpContext => - RateLimitPartition.GetFixedWindowLimiter( - partitionKey: httpContext.User.Identity?.Name ?? httpContext.Request.Headers.Host.ToString(), - factory: partition => new FixedWindowRateLimiterOptions - { - AutoReplenishment = true, - PermitLimit = 10, - QueueLimit = 0, - Window = TimeSpan.FromMinutes(1) - })); - }); - - builder.Services.AddPersistMessageProcessor(); + builder.AddPersistMessageProcessor(nameof(PersistMessage)); builder.AddMongoDbContext(); builder.Services.AddEndpointsApiExplorer(); - builder.AddCustomSerilog(env); builder.Services.AddJwt(); builder.Services.AddHttpContextAccessor(); builder.Services.AddAspnetOpenApi(); @@ -71,9 +73,7 @@ public static class InfrastructureExtensions builder.Services.AddValidatorsFromAssembly(typeof(BookingRoot).Assembly); builder.Services.AddProblemDetails(); builder.Services.AddCustomMapster(typeof(BookingRoot).Assembly); - builder.Services.AddCustomHealthCheck(); builder.Services.AddCustomMassTransit(env, TransportType.RabbitMq, typeof(BookingRoot).Assembly); - builder.AddCustomObservability(); builder.Services.AddTransient(); // ref: https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/EventStoreDB/ECommerce @@ -91,15 +91,14 @@ public static class InfrastructureExtensions var env = app.Environment; var appOptions = app.GetOptions(nameof(AppOptions)); + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseCustomHealthCheck(); app.UseCustomObservability(); app.UseCustomProblemDetails(); - app.UseSerilogRequestLogging(options => - { - options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest; - }); app.UseCorrelationId(); - app.UseCustomHealthCheck(); app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name)); if (env.IsDevelopment()) diff --git a/src/Services/Flight/src/Flight.Api/Program.cs b/src/Services/Flight/src/Flight.Api/Program.cs index d1c71d4..f388ff5 100644 --- a/src/Services/Flight/src/Flight.Api/Program.cs +++ b/src/Services/Flight/src/Flight.Api/Program.cs @@ -4,14 +4,6 @@ using Flight.Extensions.Infrastructure; var builder = WebApplication.CreateBuilder(args); -builder.Host.UseDefaultServiceProvider((context, options) => -{ - // Service provider validation - // ref: https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/ - options.ValidateScopes = context.HostingEnvironment.IsDevelopment() || context.HostingEnvironment.IsStaging() || context.HostingEnvironment.IsEnvironment("tests"); - options.ValidateOnBuild = true; -}); - builder.AddMinimalEndpoints(assemblies: typeof(FlightRoot).Assembly); builder.AddInfrastructure(); @@ -19,8 +11,6 @@ var app = builder.Build(); // ref: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-7.0#routing-basics app.MapMinimalEndpoints(); -app.UseAuthentication(); -app.UseAuthorization(); app.UseInfrastructure(); app.Run(); diff --git a/src/Services/Flight/src/Flight.Api/appsettings.docker.json b/src/Services/Flight/src/Flight.Api/appsettings.docker.json index 68c50c4..9fe5281 100644 --- a/src/Services/Flight/src/Flight.Api/appsettings.docker.json +++ b/src/Services/Flight/src/Flight.Api/appsettings.docker.json @@ -1,36 +1,32 @@ { - "App": "Flight-Service", - "LogOptions": { - "Level": "information", - "LogTemplate": "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", - "File": { - "Enabled": false, - "Path": "logs/logs.txt", - "Interval": "day" - } - }, - "PostgresOptions": { - "ConnectionString": "Server=postgres;Port=5432;Database=flight;User Id=postgres;Password=postgres;Include Error Detail=true" - }, - "Jwt": { - "Authority": "http://identity:80", - "Audience": "flight-api" - }, - "RabbitMqOptions": { - "HostName": "rabbitmq", - "ExchangeName": "flight", - "UserName": "guest", - "Password": "guest", - "Port": 5672 - }, - "PersistMessageOptions": { - "Interval": 30, - "Enabled": true, - "ConnectionString": "Server=postgres;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true" - }, - "MongoOptions": { - "ConnectionString": "mongodb://mongo:27017", - "DatabaseName": "flight-db" - }, - "AllowedHosts": "*" + "App": "Flight-Service", + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, + "PostgresOptions": { + "ConnectionString": "Server=postgres;Port=5432;Database=flight;User Id=postgres;Password=postgres;Include Error Detail=true" + }, + "Jwt": { + "Authority": "http://identity:80", + "Audience": "flight-api" + }, + "RabbitMqOptions": { + "HostName": "rabbitmq", + "ExchangeName": "flight", + "UserName": "guest", + "Password": "guest", + "Port": 5672 + }, + "PersistMessageOptions": { + "Interval": 30, + "Enabled": true, + "ConnectionString": "Server=postgres;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true" + }, + "MongoOptions": { + "ConnectionString": "mongodb://mongo:27017", + "DatabaseName": "flight-db" + }, + "AllowedHosts": "*" } diff --git a/src/Services/Flight/src/Flight.Api/appsettings.json b/src/Services/Flight/src/Flight.Api/appsettings.json index a4cc3a8..5b73f0c 100644 --- a/src/Services/Flight/src/Flight.Api/appsettings.json +++ b/src/Services/Flight/src/Flight.Api/appsettings.json @@ -2,13 +2,9 @@ "AppOptions": { "Name": "Flight-Service" }, - "LogOptions": { - "Level": "information", - "LogTemplate": "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", - "File": { - "Enabled": false, - "Path": "logs/logs.txt", - "Interval": "day" + "Logging": { + "LogLevel": { + "Default": "Information" } }, "PostgresOptions": { diff --git a/src/Services/Flight/src/Flight.Api/appsettings.test.json b/src/Services/Flight/src/Flight.Api/appsettings.test.json index 9a7de5c..ab263a0 100644 --- a/src/Services/Flight/src/Flight.Api/appsettings.test.json +++ b/src/Services/Flight/src/Flight.Api/appsettings.test.json @@ -1,25 +1,22 @@ { - "PostgresOptions": { - "ConnectionString": "Server=localhost;Port=5432;Database=flight_test;User Id=postgres;Password=postgres;Include Error Detail=true" - }, - "RabbitMqOptions": { - "HostName": "localhost", - "ExchangeName": "flight", - "UserName": "guest", - "Password": "guest", - "Port": 5672 - }, - "Logging": { - "LogLevel": { - "Default": "Debug", - "Microsoft": "Debug", - "Microsoft.Hosting.Lifetime": "Debug", - "Microsoft.EntityFrameworkCore.Database.Command": "Debug" + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, + "PostgresOptions": { + "ConnectionString": "Server=localhost;Port=5432;Database=flight_test;User Id=postgres;Password=postgres;Include Error Detail=true" + }, + "RabbitMqOptions": { + "HostName": "localhost", + "ExchangeName": "flight", + "UserName": "guest", + "Password": "guest", + "Port": 5672 + }, + "PersistMessageOptions": { + "Interval": 2, + "Enabled": true, + "ConnectionString": "Server=localhost;Port=5432;Database=persist_message_test;User Id=postgres;Password=postgres;Include Error Detail=true" } - }, - "PersistMessageOptions": { - "Interval": 2, - "Enabled": true, - "ConnectionString": "Server=localhost;Port=5432;Database=persist_message_test;User Id=postgres;Password=postgres;Include Error Detail=true" - } } diff --git a/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs index efb8ec7..2a40d92 100644 --- a/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -1,10 +1,8 @@ -using System.Threading.RateLimiting; using BuildingBlocks.Core; using BuildingBlocks.EFCore; using BuildingBlocks.Exception; using BuildingBlocks.HealthCheck; using BuildingBlocks.Jwt; -using BuildingBlocks.Logging; using BuildingBlocks.Mapster; using BuildingBlocks.MassTransit; using BuildingBlocks.Mongo; @@ -23,7 +21,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Serilog; namespace Flight.Extensions.Infrastructure; @@ -35,6 +32,26 @@ public static class InfrastructureExtensions var configuration = builder.Configuration; var env = builder.Environment; + builder.Services.AddCustomHealthCheck(); + + builder.AddCustomObservability(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + http.AddStandardResilienceHandler(options => + { + var timeSpan = TimeSpan.FromMinutes(1); + options.CircuitBreaker.SamplingDuration = timeSpan * 2; + options.TotalRequestTimeout.Timeout = timeSpan * 3; + options.Retry.MaxRetryAttempts = 3; + }); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -50,27 +67,12 @@ public static class InfrastructureExtensions var appOptions = builder.Services.GetOptions(nameof(AppOptions)); Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name)); - builder.Services.AddRateLimiter(options => - { - options.GlobalLimiter = PartitionedRateLimiter.Create(httpContext => - RateLimitPartition.GetFixedWindowLimiter( - partitionKey: httpContext.User.Identity?.Name ?? httpContext.Request.Headers.Host.ToString(), - factory: partition => new FixedWindowRateLimiterOptions - { - AutoReplenishment = true, - PermitLimit = 10, - QueueLimit = 0, - Window = TimeSpan.FromMinutes(1) - })); - }); - - builder.AddCustomDbContext(); + builder.AddCustomDbContext(nameof(Flight)); builder.Services.AddScoped(); builder.AddMongoDbContext(); - builder.Services.AddPersistMessageProcessor(); + builder.AddPersistMessageProcessor(nameof(PersistMessage)); builder.Services.AddEndpointsApiExplorer(); - builder.AddCustomSerilog(env); builder.Services.AddJwt(); builder.Services.AddAspnetOpenApi(); builder.Services.AddCustomVersioning(); @@ -78,8 +80,6 @@ public static class InfrastructureExtensions builder.Services.AddCustomMapster(typeof(FlightRoot).Assembly); builder.Services.AddHttpContextAccessor(); builder.Services.AddCustomMassTransit(env, TransportType.RabbitMq, typeof(FlightRoot).Assembly); - builder.AddCustomObservability(); - builder.Services.AddCustomHealthCheck(); builder.Services.AddGrpc(options => { @@ -97,18 +97,17 @@ public static class InfrastructureExtensions var env = app.Environment; var appOptions = app.GetOptions(nameof(AppOptions)); + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseCustomHealthCheck(); app.UseCustomObservability(); app.UseCustomProblemDetails(); - app.UseSerilogRequestLogging(options => - { - options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest; - }); app.UseCorrelationId(); app.UseMigration(); - app.UseCustomHealthCheck(); app.MapGrpcService(); - app.UseRateLimiter(); + app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name)); if (env.IsDevelopment()) diff --git a/src/Services/Identity/src/Identity.Api/Program.cs b/src/Services/Identity/src/Identity.Api/Program.cs index 7de6353..9ef9072 100644 --- a/src/Services/Identity/src/Identity.Api/Program.cs +++ b/src/Services/Identity/src/Identity.Api/Program.cs @@ -1,26 +1,15 @@ using BuildingBlocks.Web; using Identity; -using Identity.Configurations; using Identity.Extensions.Infrastructure; var builder = WebApplication.CreateBuilder(args); -builder.Host.UseDefaultServiceProvider((context, options) => -{ - // Service provider validation - // ref: https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/ - options.ValidateScopes = context.HostingEnvironment.IsDevelopment() || context.HostingEnvironment.IsStaging() || context.HostingEnvironment.IsEnvironment("tests"); - options.ValidateOnBuild = true; -}); - builder.AddMinimalEndpoints(assemblies: typeof(IdentityRoot).Assembly); builder.AddInfrastructure(); var app = builder.Build(); app.MapMinimalEndpoints(); -app.UseAuthentication(); -app.UseAuthorization(); app.UseInfrastructure(); app.Run(); diff --git a/src/Services/Identity/src/Identity.Api/appsettings.docker.json b/src/Services/Identity/src/Identity.Api/appsettings.docker.json index 967ec5d..e29ab03 100644 --- a/src/Services/Identity/src/Identity.Api/appsettings.docker.json +++ b/src/Services/Identity/src/Identity.Api/appsettings.docker.json @@ -1,31 +1,27 @@ { - "App": "Identity-Service", - "PostgresOptions": { - "ConnectionString": "Server=postgres;Port=5432;Database=identity;User Id=postgres;Password=postgres;Include Error Detail=true" - }, - "PersistMessageOptions": { - "Interval": 30, - "Enabled": true, - "ConnectionString": "Server=postgres;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true" - }, - "AuthOptions": { - "IssuerUri": "http://identity:80" - }, - "RabbitMqOptions": { - "HostName": "rabbitmq", - "ExchangeName": "identity", - "UserName": "guest", - "Password": "guest", - "Port": 5672 - }, - "LogOptions": { - "Level": "information", - "LogTemplate": "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", - "File": { - "Enabled": false, - "Path": "logs/logs.txt", - "Interval": "day" - } - }, - "AllowedHosts": "*" + "App": "Identity-Service", + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, + "PostgresOptions": { + "ConnectionString": "Server=postgres;Port=5432;Database=identity;User Id=postgres;Password=postgres;Include Error Detail=true" + }, + "PersistMessageOptions": { + "Interval": 30, + "Enabled": true, + "ConnectionString": "Server=postgres;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true" + }, + "AuthOptions": { + "IssuerUri": "http://identity:80" + }, + "RabbitMqOptions": { + "HostName": "rabbitmq", + "ExchangeName": "identity", + "UserName": "guest", + "Password": "guest", + "Port": 5672 + }, + "AllowedHosts": "*" } diff --git a/src/Services/Identity/src/Identity.Api/appsettings.json b/src/Services/Identity/src/Identity.Api/appsettings.json index f144a55..ffbc11d 100644 --- a/src/Services/Identity/src/Identity.Api/appsettings.json +++ b/src/Services/Identity/src/Identity.Api/appsettings.json @@ -2,6 +2,11 @@ "AppOptions": { "Name": "Identity-Service" }, + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, "PostgresOptions": { "ConnectionString": "Server=localhost;Port=5432;Database=identity;User Id=postgres;Password=postgres;Include Error Detail=true" }, @@ -15,23 +20,14 @@ "Password": "guest", "Port": 5672 }, - "LogOptions": { - "Level": "information", - "LogTemplate": "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", - "File": { - "Enabled": false, - "Path": "logs/logs.txt", - "Interval": "day" - } - }, - "HealthOptions": { - "Enabled": false - }, "PersistMessageOptions": { "Interval": 30, "Enabled": true, "ConnectionString": "Server=localhost;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true" }, + "HealthOptions": { + "Enabled": false + }, "ObservabilityOptions": { "InstrumentationName": "identity_service", "OTLPOptions": { diff --git a/src/Services/Identity/src/Identity.Api/appsettings.test.json b/src/Services/Identity/src/Identity.Api/appsettings.test.json index 085eaac..1b31a9d 100644 --- a/src/Services/Identity/src/Identity.Api/appsettings.test.json +++ b/src/Services/Identity/src/Identity.Api/appsettings.test.json @@ -1,28 +1,25 @@ { - "PostgresOptions": { - "ConnectionString": "Server=localhost;Port=5432;Database=identity_test;User Id=postgres;Password=postgres;Include Error Detail=true" - }, - "AuthOptions": { - "IssuerUri": "http://localhost:6005" - }, - "RabbitMqOptions": { - "HostName": "localhost", - "ExchangeName": "identity", - "UserName": "guest", - "Password": "guest", - "Port": 5672 - }, - "Logging": { - "LogLevel": { - "Default": "Debug", - "Microsoft": "Debug", - "Microsoft.Hosting.Lifetime": "Debug", - "Microsoft.EntityFrameworkCore.Database.Command": "Debug" + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, + "PostgresOptions": { + "ConnectionString": "Server=localhost;Port=5432;Database=identity_test;User Id=postgres;Password=postgres;Include Error Detail=true" + }, + "AuthOptions": { + "IssuerUri": "http://localhost:6005" + }, + "RabbitMqOptions": { + "HostName": "localhost", + "ExchangeName": "identity", + "UserName": "guest", + "Password": "guest", + "Port": 5672 + }, + "PersistMessageOptions": { + "Interval": 30, + "Enabled": true, + "ConnectionString": "Server=localhost;Port=5432;Database=persist_message_test;User Id=postgres;Password=postgres;Include Error Detail=true" } - }, - "PersistMessageOptions": { - "Interval": 30, - "Enabled": true, - "ConnectionString": "Server=localhost;Port=5432;Database=persist_message_test;User Id=postgres;Password=postgres;Include Error Detail=true" - } } diff --git a/src/Services/Identity/src/Identity.Api/keys/is-signing-key-E1668D5B7CCDD18C610506FCA7C5D194.json b/src/Services/Identity/src/Identity.Api/keys/is-signing-key-E1668D5B7CCDD18C610506FCA7C5D194.json new file mode 100644 index 0000000..e0eef40 --- /dev/null +++ b/src/Services/Identity/src/Identity.Api/keys/is-signing-key-E1668D5B7CCDD18C610506FCA7C5D194.json @@ -0,0 +1 @@ +{"Version":1,"Id":"E1668D5B7CCDD18C610506FCA7C5D194","Created":"2025-07-21T17:14:15.7672364Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8Ai8D9hfDhJIplL3Rv2bStBd2WatjNqn9OUgTfnOqxduEkXmsnrgWiHbuP-AYTE2ZeIxoDSDbZWBD8dJbAMe5PfH-FC7E5njE18xMIRyRxxvLHBcINyJ3fAP7lk5-uTl_F2DhoY6S_DCtbgbXCoB57FPr8DJzMPN0q37yuC34ZQYhfgLbkfydesVGqW1DF2_a5pd2KAFK_m8_FVy1GfHvTFnLjuURASp8eKEDJrtii4683uplUkycVJRHfHHQta1PuJW3KOLcX9jSePWDEBnQkjsiHm2O3BQFDdsMHbr8F2cLGW9uMIYNd96QyiH7nUgVZ0i5R-sHpbnGzUZFnZvpWLJFZrT3269nBLL2-t-7Sh99laIIT54cdZsW_uGFZ7r1MqyEPt9MoHqXXuOuhrn22QQeQURGmyUt4YyxdKCozsEPs7MR4IR_1iY93Rv4ThEn24vq1-guK-58pu3DurgigbTB7hFToOtqBHEhiVyXKgoNGOXH7UW5bJLFj4JtOIN_2SaTohhw9pSf8yH7gf6Chp2aVGUQt0OnVLbLu__EbB9XzJblQHLi6Ddm8uXdrJYXW17iBdEUHL8xD4hOiE4SR-bhD_d8c9yD7ydSyIwdistrVFRYpeDc6n-kQLWC6r1Z1oxIQte7ph6n3ygAWSwyCzSkI1GKJoh6QdR6rrs4EN9jUdFN4NgjQqftlLaE6xKq3f82wbCTk5-Iy8jJlr-xdDue_myN6Z_XOJ01pyjDkX9u3Aa0UuNFlvGYde9bJa6R7VxE5ggVozjpB_3FNUc91_3merwjq3K5-3S3EnhU8Nxi44Yjsw6-pVLbyF0Tgbh3uGUWI4EVv_nUC94cLaftuutq6rApSs1lF1jq_RK2P9YBQyCNl23JYMWEkPscMGIrx0306eLpWelYu_TpLk2lj5JfM7ep4uQ714bQXMLAdNX9GttAOOKlTrb4--uz8kjHaOSaJ01tvutQpog-RrudUVHX46_EA43U0jazAVQJXX7uDC5CEiSfCXozk7Gvw56gk72Ha1Ygz_qfaSn1hv6Q4cHhuhHzRV_aGui5oztal-oSh-zd_6BobstL1iZHhukHrUq_gVcFhYnqNDgzKT1L21AV9d0LbqONkFZ6kBGZ7ZIE2ymIqRx4DgBCdkmgZZUAZRqK3wkYW3oKI18l1aF7dGrrUnRSFVg5Jr0BYSmVPBVpfGLUHb3VHpjPVxPMV2W-ak0JG01jL7OQBuKXUW115Qd89GE2rj_SnInDg6ljW94zDHbqb_8gH3WOSWawM6021yQlRpMe7xqv6bYVeAS6WZBphKPxLV3BtG0_23IOiEbvsreT6UvIn9hx-VkJ_CADmlKQ8uNFxUIDvop6vGq5zjPgQz2-kJ529MUrp-ayn-oXTOrRgrYzLsUAH-gzf_osIMKsGPQakQMmnL7jsyX0z7Ci2wKuZ7pgxw8DQk8PKrk_yNuTMRQN0etUvv-nSCtPbcHALqMN9VgcHgCEEZTTP4A0pUUNAWewd2hJoh12H4_JlyJirHFDK5ZyM_uWKjAfVbh4RFLnxPaEEdQsew-WgXXE4Ne965OG283Q7TCv2MUCh-UynOz2ySw1FKD5XeqkOYmIXlrGf7DuhXQcam1SW9_8N5k9_lIdG_QPW6cM7HR3amBiHXnR7I-MBmTf3CLirEu_CrP6fdcGjrWBtbpfHCOPXNUseltmEWc0Ph3FoIYSR7F8o7cuYHWm0cuK3yC_73OXSDuDWmkl79qhyy3FJHV-6RTlsiyIZiOZ6Sxg24NKr5lp8FoqEZ2iQIKPfIPIYuD4YGmRKkk4WTV_slY68upm06xojpzQLs_resmmSc-WmkkRMgmuXaOxQr5oqZEFFqC3XLU3LAQkmOjMfJk4DWYWBrT-WzkfRJLmXhi3_DH7xh4IQ6t6Patumr-DT70FLySntRsvGS-wcI9U_w39gfGupOU6ytZFN_0lcNrYWR_0K_IjD_lmEUPdywFNs1_EbrIFSPo9pKZRveo1HLcSEatZju_JGqIT0lUQ0nZDIximnODr7w217fA_Vq9pLoMZw3AvT42vQqeg3jqoROF_fkz43fMmCNdZlhLuRUQ_IjZ5qmgKEaodtM8ZtgvB0qJNn_5NzX7ArreV-XQpOf8TxOvIWwmM4C9Ra3-Uo0CDrgIwxxtW9QjnlLx__Nb_Pcvf63dTtvt8y6FLFki3eeA_Q0IC4v0oKRQAVd1BtybbTMYMojXsrc1WElijhfDvybG04y6oWvh0GWx97FYk2bx3bTTi5EB-tXmnD12V2Jigw1e1x4rWpFh28A5eO6o5ymX1KRFdgG2nV45__NmJoZxmgWFFmTkNQSpHgl05NRRzhbcIrAzfAFnMQxdzrOCg4omRzf3yygQEBvuzpfThoZr3eaDdMCtaUuS_eUABIFXAYsRQpI33nhK4ac11S_m7k_vUCc1OWZ7mVLodyVqF3b3vqDIHw-EFNppSUUlpUzeLN2Q","DataProtected":true} \ No newline at end of file diff --git a/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs index c88be99..b9d26c2 100644 --- a/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -1,8 +1,6 @@ -using System.Threading.RateLimiting; using BuildingBlocks.Core; using BuildingBlocks.EFCore; using BuildingBlocks.HealthCheck; -using BuildingBlocks.Logging; using BuildingBlocks.Mapster; using BuildingBlocks.MassTransit; using BuildingBlocks.OpenApi; @@ -12,7 +10,6 @@ using BuildingBlocks.ProblemDetails; using BuildingBlocks.Web; using Figgle; using FluentValidation; -using Identity.Configurations; using Identity.Data; using Identity.Data.Seed; using Microsoft.AspNetCore.Builder; @@ -21,11 +18,9 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Serilog; namespace Identity.Extensions.Infrastructure; - public static class InfrastructureExtensions { public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder) @@ -33,6 +28,26 @@ public static class InfrastructureExtensions var configuration = builder.Configuration; var env = builder.Environment; + builder.Services.AddCustomHealthCheck(); + + builder.AddCustomObservability(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + http.AddStandardResilienceHandler(options => + { + var timeSpan = TimeSpan.FromMinutes(1); + options.CircuitBreaker.SamplingDuration = timeSpan * 2; + options.TotalRequestTimeout.Timeout = timeSpan * 3; + options.Retry.MaxRetryAttempts = 3; + }); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -45,36 +60,19 @@ public static class InfrastructureExtensions var appOptions = builder.Services.GetOptions(nameof(AppOptions)); Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name)); - builder.Services.AddRateLimiter(options => - { - options.GlobalLimiter = PartitionedRateLimiter.Create(httpContext => - RateLimitPartition.GetFixedWindowLimiter( - partitionKey: httpContext.User.Identity?.Name ?? httpContext.Request.Headers.Host.ToString(), - factory: partition => new FixedWindowRateLimiterOptions - { - AutoReplenishment = true, - PermitLimit = 10, - QueueLimit = 0, - Window = TimeSpan.FromMinutes(1) - })); - }); - builder.Services.AddEndpointsApiExplorer(); builder.Services.AddControllers(); - builder.Services.AddPersistMessageProcessor(); - builder.AddCustomDbContext(); + builder.AddPersistMessageProcessor(); + builder.AddCustomDbContext(nameof(Identity)); builder.Services.AddScoped(); - builder.AddCustomSerilog(env); builder.Services.AddAspnetOpenApi(); builder.Services.AddCustomVersioning(); builder.Services.AddCustomMediatR(); builder.Services.AddValidatorsFromAssembly(typeof(IdentityRoot).Assembly); builder.Services.AddProblemDetails(); builder.Services.AddCustomMapster(typeof(IdentityRoot).Assembly); - builder.Services.AddCustomHealthCheck(); builder.Services.AddCustomMassTransit(env, TransportType.RabbitMq, typeof(IdentityRoot).Assembly); - builder.AddCustomObservability(); builder.AddCustomIdentityServer(); @@ -93,18 +91,17 @@ public static class InfrastructureExtensions var env = app.Environment; var appOptions = app.GetOptions(nameof(AppOptions)); + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseCustomHealthCheck(); app.UseCustomObservability(); app.UseForwardedHeaders(); app.UseCustomProblemDetails(); - app.UseSerilogRequestLogging(options => - { - options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest; - }); app.UseCorrelationId(); app.UseMigration(); - app.UseCustomHealthCheck(); app.UseIdentityServer(); app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name)); diff --git a/src/Services/Identity/tests/IntegrationTest/Identity/Features/RegisterNewUserTests.cs b/src/Services/Identity/tests/IntegrationTest/Identity/Features/RegisterNewUserTests.cs index f606fef..1593d4f 100644 --- a/src/Services/Identity/tests/IntegrationTest/Identity/Features/RegisterNewUserTests.cs +++ b/src/Services/Identity/tests/IntegrationTest/Identity/Features/RegisterNewUserTests.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.TestBase; using FluentAssertions; diff --git a/src/Services/Passenger/src/Passenger.Api/Program.cs b/src/Services/Passenger/src/Passenger.Api/Program.cs index 5d1aeac..66303f3 100644 --- a/src/Services/Passenger/src/Passenger.Api/Program.cs +++ b/src/Services/Passenger/src/Passenger.Api/Program.cs @@ -4,22 +4,12 @@ using Passenger.Extensions.Infrastructure; var builder = WebApplication.CreateBuilder(args); -builder.Host.UseDefaultServiceProvider((context, options) => -{ - // Service provider validation - // ref: https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/ - options.ValidateScopes = context.HostingEnvironment.IsDevelopment() || context.HostingEnvironment.IsStaging() || context.HostingEnvironment.IsEnvironment("tests"); - options.ValidateOnBuild = true; -}); - builder.AddMinimalEndpoints(assemblies: typeof(PassengerRoot).Assembly); builder.AddInfrastructure(); var app = builder.Build(); app.MapMinimalEndpoints(); -app.UseAuthentication(); -app.UseAuthorization(); app.UseInfrastructure(); app.Run(); diff --git a/src/Services/Passenger/src/Passenger.Api/appsettings.docker.json b/src/Services/Passenger/src/Passenger.Api/appsettings.docker.json index 3254370..d23e69c 100644 --- a/src/Services/Passenger/src/Passenger.Api/appsettings.docker.json +++ b/src/Services/Passenger/src/Passenger.Api/appsettings.docker.json @@ -1,36 +1,32 @@ { - "App": "Passenger-Service", - "PostgresOptions": { - "ConnectionString": "Server=postgres;Port=5432;Database=passenger;User Id=postgres;Password=postgres;Include Error Detail=true" - }, - "PersistMessageOptions": { - "Interval": 30, - "Enabled": true, - "ConnectionString": "Server=postgres;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true" - }, - "Jwt": { - "Authority": "http://identity:80", - "Audience": "passenger-api" - }, - "MongoOptions": { - "ConnectionString": "mongodb://mongo:27017", - "DatabaseName": "passenger-db" - }, - "RabbitMqOptions": { - "HostName": "rabbitmq", - "ExchangeName": "passenger", - "UserName": "guest", - "Password": "guest", - "Port": 5672 - }, - "LogOptions": { - "Level": "information", - "LogTemplate": "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", - "File": { - "Enabled": false, - "Path": "logs/logs.txt", - "Interval": "day" - } - }, - "AllowedHosts": "*" + "App": "Passenger-Service", + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, + "PostgresOptions": { + "ConnectionString": "Server=postgres;Port=5432;Database=passenger;User Id=postgres;Password=postgres;Include Error Detail=true" + }, + "PersistMessageOptions": { + "Interval": 30, + "Enabled": true, + "ConnectionString": "Server=postgres;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true" + }, + "Jwt": { + "Authority": "http://identity:80", + "Audience": "passenger-api" + }, + "MongoOptions": { + "ConnectionString": "mongodb://mongo:27017", + "DatabaseName": "passenger-db" + }, + "RabbitMqOptions": { + "HostName": "rabbitmq", + "ExchangeName": "passenger", + "UserName": "guest", + "Password": "guest", + "Port": 5672 + }, + "AllowedHosts": "*" } diff --git a/src/Services/Passenger/src/Passenger.Api/appsettings.json b/src/Services/Passenger/src/Passenger.Api/appsettings.json index 6f28cf4..ff5a093 100644 --- a/src/Services/Passenger/src/Passenger.Api/appsettings.json +++ b/src/Services/Passenger/src/Passenger.Api/appsettings.json @@ -2,6 +2,11 @@ "AppOptions": { "Name": "Passenger-Service" }, + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, "PostgresOptions": { "ConnectionString": "Server=localhost;Port=5432;Database=passenger;User Id=postgres;Password=postgres;Include Error Detail=true" }, @@ -20,23 +25,14 @@ "Password": "guest", "Port": 5672 }, - "LogOptions": { - "Level": "information", - "LogTemplate": "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", - "File": { - "Enabled": false, - "Path": "logs/logs.txt", - "Interval": "day" - } - }, - "HealthOptions": { - "Enabled": false - }, "PersistMessageOptions": { "Interval": 30, "Enabled": true, "ConnectionString": "Server=localhost;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true" }, + "HealthOptions": { + "Enabled": false + }, "ObservabilityOptions": { "InstrumentationName": "passenger_service", "OTLPOptions": { diff --git a/src/Services/Passenger/src/Passenger.Api/appsettings.test.json b/src/Services/Passenger/src/Passenger.Api/appsettings.test.json index 134759f..47809b0 100644 --- a/src/Services/Passenger/src/Passenger.Api/appsettings.test.json +++ b/src/Services/Passenger/src/Passenger.Api/appsettings.test.json @@ -1,25 +1,22 @@ { - "PostgresOptions": { - "ConnectionString": "Server=localhost;Port=5432;Database=passenger_test;User Id=postgres;Password=postgres;Include Error Detail=true" - }, - "RabbitMqOptions": { - "HostName": "localhost", - "ExchangeName": "passenger", - "UserName": "guest", - "Password": "guest", - "Port": 5672 - }, - "Logging": { - "LogLevel": { - "Default": "Debug", - "Microsoft": "Debug", - "Microsoft.Hosting.Lifetime": "Debug", - "Microsoft.EntityFrameworkCore.Database.Command": "Debug" + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, + "PostgresOptions": { + "ConnectionString": "Server=localhost;Port=5432;Database=passenger_test;User Id=postgres;Password=postgres;Include Error Detail=true" + }, + "RabbitMqOptions": { + "HostName": "localhost", + "ExchangeName": "passenger", + "UserName": "guest", + "Password": "guest", + "Port": 5672 + }, + "PersistMessageOptions": { + "Interval": 30, + "Enabled": true, + "ConnectionString": "Server=localhost;Port=5432;Database=persist_message_test;User Id=postgres;Password=postgres;Include Error Detail=true" } - }, - "PersistMessageOptions": { - "Interval": 30, - "Enabled": true, - "ConnectionString": "Server=localhost;Port=5432;Database=persist_message_test;User Id=postgres;Password=postgres;Include Error Detail=true" - } } diff --git a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs index ba27182..7e1f10a 100644 --- a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -1,10 +1,8 @@ -using System.Threading.RateLimiting; using BuildingBlocks.Core; using BuildingBlocks.EFCore; using BuildingBlocks.Exception; using BuildingBlocks.HealthCheck; using BuildingBlocks.Jwt; -using BuildingBlocks.Logging; using BuildingBlocks.Mapster; using BuildingBlocks.MassTransit; using BuildingBlocks.Mongo; @@ -22,7 +20,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Passenger.Data; using Passenger.GrpcServer.Services; -using Serilog; namespace Passenger.Extensions.Infrastructure; @@ -33,6 +30,27 @@ public static class InfrastructureExtensions var configuration = builder.Configuration; var env = builder.Environment; + builder.Services.AddCustomHealthCheck(); + + builder.AddCustomObservability(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + http.AddStandardResilienceHandler(options => + { + var timeSpan = TimeSpan.FromMinutes(1); + options.CircuitBreaker.SamplingDuration = timeSpan * 2; + options.TotalRequestTimeout.Timeout = timeSpan * 3; + options.Retry.MaxRetryAttempts = 3; + }); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -46,28 +64,10 @@ public static class InfrastructureExtensions var appOptions = builder.Services.GetOptions(nameof(AppOptions)); Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name)); - builder.Services.AddRateLimiter( - options => - { - options.GlobalLimiter = PartitionedRateLimiter.Create( - httpContext => - RateLimitPartition.GetFixedWindowLimiter( - partitionKey: httpContext.User.Identity?.Name ?? - httpContext.Request.Headers.Host.ToString(), - factory: partition => new FixedWindowRateLimiterOptions - { - AutoReplenishment = true, - PermitLimit = 10, - QueueLimit = 0, - Window = TimeSpan.FromMinutes(1) - })); - }); - - builder.Services.AddPersistMessageProcessor(); - builder.AddCustomDbContext(); + builder.AddPersistMessageProcessor(nameof(PersistMessage)); + builder.AddCustomDbContext(nameof(Passenger)); builder.AddMongoDbContext(); - builder.AddCustomSerilog(env); builder.Services.AddJwt(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddAspnetOpenApi(); @@ -77,9 +77,7 @@ public static class InfrastructureExtensions builder.Services.AddProblemDetails(); builder.Services.AddCustomMapster(typeof(PassengerRoot).Assembly); builder.Services.AddHttpContextAccessor(); - builder.Services.AddCustomHealthCheck(); builder.Services.AddCustomMassTransit(env, TransportType.RabbitMq, typeof(PassengerRoot).Assembly); - builder.AddCustomObservability(); builder.Services.AddGrpc( options => @@ -96,19 +94,16 @@ public static class InfrastructureExtensions var env = app.Environment; var appOptions = app.GetOptions(nameof(AppOptions)); + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseCustomHealthCheck(); app.UseCustomObservability(); app.UseCustomProblemDetails(); - app.UseSerilogRequestLogging( - options => - { - options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest; - }); - app.UseCorrelationId(); app.UseMigration(); - app.UseCustomHealthCheck(); app.MapGrpcService(); app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));