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));