mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-11 02:20:20 +08:00
Merge pull request #355 from meysamhadeli/feat/add-aspire-integrations
feat: add .net aspire integrations
This commit is contained in:
commit
756f166711
@ -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
|
||||
|
||||
@ -358,4 +358,5 @@ networks:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
elastic-data:
|
||||
postgres-data:
|
||||
@ -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"
|
||||
|
||||
@ -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();
|
||||
|
||||
32
src/Aspire/src/AppHost/AppHost.csproj
Normal file
32
src/Aspire/src/AppHost/AppHost.csproj
Normal file
@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Sdk Name="Aspire.AppHost.SDK" Version="9.3.1"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsAspireHost>true</IsAspireHost>
|
||||
<UserSecretsId>bde28db3-85ba-4201-b889-0f3faba24169</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.3.1" />
|
||||
<PackageReference Include="Aspire.Hosting.MongoDB" Version="9.3.1" />
|
||||
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.3.1" />
|
||||
<PackageReference Include="Aspire.Hosting.RabbitMQ" Version="9.3.1" />
|
||||
<PackageReference Include="Aspire.Hosting.Redis" Version="9.3.1" />
|
||||
<PackageReference Include="CommunityToolkit.Aspire.Hosting.EventStore" Version="9.6.0" />
|
||||
<PackageReference Include="Elastic.Aspire.Hosting.Elasticsearch" Version="9.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\ApiGateway\src\ApiGateway.csproj" />
|
||||
<ProjectReference Include="..\..\..\Services\Booking\src\Booking.Api\Booking.Api.csproj" />
|
||||
<ProjectReference Include="..\..\..\Services\Flight\src\Flight.Api\Flight.Api.csproj" />
|
||||
<ProjectReference Include="..\..\..\Services\Identity\src\Identity.Api\Identity.Api.csproj" />
|
||||
<ProjectReference Include="..\..\..\Services\Passenger\src\Passenger.Api\Passenger.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
216
src/Aspire/src/AppHost/Program.cs
Normal file
216
src/Aspire/src/AppHost/Program.cs
Normal file
@ -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<Projects.Identity_Api>("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<Projects.Passenger_Api>("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<Projects.Flight_Api>("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<Projects.Booking_Api>("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<Projects.ApiGateway>("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();
|
||||
30
src/Aspire/src/AppHost/Properties/launchSettings.json
Normal file
30
src/Aspire/src/AppHost/Properties/launchSettings.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Aspire/src/AppHost/appsettings.Development.json
Normal file
8
src/Aspire/src/AppHost/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Aspire/src/AppHost/appsettings.json
Normal file
9
src/Aspire/src/AppHost/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@ -6,16 +6,6 @@
|
||||
<PackageReference Include="Asp.Versioning.Http" Version="8.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="8.0.2" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.1" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="8.0.1" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Elasticsearch" Version="8.0.1" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.EventStore" Version="8.0.1" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" Version="8.0.2" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="8.0.2" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.SQLite.Storage" Version="8.0.1" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="8.1.0" />
|
||||
<PackageReference Include="Grpc.Core.Testing" Version="2.46.6" />
|
||||
<PackageReference Include="EasyCaching.Core" Version="1.9.2" />
|
||||
<PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
|
||||
@ -27,6 +17,8 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.3.1" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.1" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Polly" Version="8.5.0" />
|
||||
@ -50,16 +42,6 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="1.2.64" />
|
||||
<PackageReference Include="Scrutor" Version="5.0.2" />
|
||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageReference Include="Serilog.Enrichers.Span" Version="3.1.0" />
|
||||
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
|
||||
<PackageReference Include="Serilog.Formatting.Elasticsearch" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.SpectreConsole" Version="0.3.3" />
|
||||
<PackageReference Include="Serilog.Sinks.XUnit" Version="3.0.5" />
|
||||
<PackageReference Include="Sieve" Version="2.5.5" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="7.1.0" />
|
||||
@ -79,7 +61,18 @@
|
||||
<PackageReference Include="Testcontainers.PostgreSql" Version="4.0.0" />
|
||||
<PackageReference Include="Testcontainers.RabbitMq" Version="4.0.0" />
|
||||
<PackageReference Include="Unchase.Swashbuckle.AspNetCore.Extensions" Version="2.7.1" />
|
||||
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
|
||||
<PackageReference Include="xunit.extensibility.core" Version="2.9.2" />
|
||||
<PackageReference Include="Xunit.Extensions.Logging" Version="1.1.0" />
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.EventStore" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="9.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="9.0.0" />
|
||||
|
||||
<PackageReference Include="Npgsql.OpenTelemetry" Version="9.0.1" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.11.1" />
|
||||
|
||||
@ -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<TContext>(this WebApplicationBuilder builder, string connectionName = "")
|
||||
public static IServiceCollection AddCustomDbContext<TContext>(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<TContext>(
|
||||
(sp, options) =>
|
||||
{
|
||||
string? connectionString = string.IsNullOrEmpty(connectionName) ?
|
||||
sp.GetRequiredService<PostgresOptions>().ConnectionString :
|
||||
builder.Configuration?.GetSection("PostgresOptions:ConnectionString")[connectionName];
|
||||
var aspireConnectionString = builder.Configuration.GetConnectionString(connectionName.Kebaberize());
|
||||
var connectionString = aspireConnectionString ?? sp.GetRequiredService<PostgresOptions>().ConnectionString;
|
||||
|
||||
ArgumentException.ThrowIfNullOrEmpty(connectionString);
|
||||
|
||||
|
||||
@ -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<EventStoreOptions>(nameof(EventStoreOptions));
|
||||
return new EventStoreClient(EventStoreClientSettings.Create(eventStoreOptions.ConnectionString));
|
||||
return new EventStoreClient(EventStoreClientSettings.Create(aspireConnectionString ?? eventStoreOptions.ConnectionString));
|
||||
})
|
||||
.AddScoped(typeof(IEventStoreDBRepository<>), typeof(EventStoreDBRepository<>))
|
||||
.AddTransient<EventStoreDBSubscriptionToAll, EventStoreDBSubscriptionToAll>();
|
||||
|
||||
@ -3,8 +3,6 @@ using BuildingBlocks.Core.Model;
|
||||
|
||||
namespace BuildingBlocks.EventStoreDB.Events
|
||||
{
|
||||
using Microsoft.FSharp.Control;
|
||||
|
||||
public interface IAggregateEventSourcing : IProjection, IEntity
|
||||
{
|
||||
IReadOnlyList<IDomainEvent> DomainEvents { get; }
|
||||
|
||||
@ -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<HealthOptions>(nameof(HealthOptions));
|
||||
|
||||
if (!healthOptions.Enabled)
|
||||
return services;
|
||||
|
||||
var appOptions = services.GetOptions<AppOptions>(nameof(AppOptions));
|
||||
var postgresOptions = services.GetOptions<PostgresOptions>(nameof(PostgresOptions));
|
||||
var rabbitMqOptions = services.GetOptions<RabbitMqOptions>(nameof(RabbitMqOptions));
|
||||
var mongoOptions = services.GetOptions<MongoOptions>(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<AppOptions>(nameof(AppOptions));
|
||||
var postgresOptions = services.GetOptions<PostgresOptions>(nameof(PostgresOptions));
|
||||
var rabbitMqOptions = services.GetOptions<RabbitMqOptions>(nameof(RabbitMqOptions));
|
||||
var eventStoreOptions = services.GetOptions<EventStoreOptions>(nameof(EventStoreOptions));
|
||||
var mongoOptions = services.GetOptions<MongoOptions>(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<HealthOptions>(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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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<LogOptions>();
|
||||
|
||||
var logLevel = Enum.TryParse<LogEventLevel>(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<RollingInterval>(logOptions.File.Interval, true, out var interval))
|
||||
{
|
||||
interval = RollingInterval.Day;
|
||||
}
|
||||
|
||||
loggerConfiguration.WriteTo.File(path, rollingInterval: interval, encoding: Encoding.UTF8, outputTemplate: logOptions.LogTemplate);
|
||||
}
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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<RabbitMqOptions>(nameof(RabbitMqOptions));
|
||||
var configuration = context.GetRequiredService<IConfiguration>();
|
||||
|
||||
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<RabbitMqOptions>(nameof(RabbitMqOptions));
|
||||
|
||||
ArgumentNullException.ThrowIfNull(rabbitMqOptions);
|
||||
|
||||
configurator.Host(
|
||||
rabbitMqOptions?.HostName,
|
||||
rabbitMqOptions?.Port ?? 5672,
|
||||
"/",
|
||||
h =>
|
||||
{
|
||||
h.Username(rabbitMqOptions.UserName);
|
||||
h.Password(rabbitMqOptions.Password);
|
||||
});
|
||||
}
|
||||
|
||||
configurator.ConfigureEndpoints(context);
|
||||
|
||||
|
||||
@ -20,7 +20,14 @@ namespace BuildingBlocks.Mongo
|
||||
where TContextService : IMongoDbContext
|
||||
where TContextImplementation : MongoDbContext, TContextService
|
||||
{
|
||||
services.Configure<MongoOptions>(configuration.GetSection(nameof(MongoOptions)));
|
||||
// Configure MongoOptions with Aspire-aware defaults
|
||||
services.AddOptions<MongoOptions>()
|
||||
.Bind(configuration.GetSection(nameof(MongoOptions)))
|
||||
.PostConfigure(options =>
|
||||
{
|
||||
var aspireConnectionString = configuration.GetConnectionString("mongo");
|
||||
options.ConnectionString = aspireConnectionString ?? options.ConnectionString;
|
||||
});
|
||||
|
||||
if (configurator is { })
|
||||
{
|
||||
|
||||
@ -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<IDiagnosticsProvider, CustomeDiagnosticsProvider>();
|
||||
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 =>
|
||||
|
||||
@ -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<PersistMessageOptions>();
|
||||
builder.Services.AddValidateOptions<PersistMessageOptions>();
|
||||
|
||||
services.AddDbContext<PersistMessageDbContext>(
|
||||
builder.Services.AddDbContext<PersistMessageDbContext>(
|
||||
(sp, options) =>
|
||||
{
|
||||
var persistMessageOptions = sp.GetRequiredService<PersistMessageOptions>();
|
||||
var aspireConnectionString = builder.Configuration.GetConnectionString(connectionName.Kebaberize());
|
||||
|
||||
var connectionString = aspireConnectionString ?? sp.GetRequiredService<PersistMessageOptions>().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<IPersistMessageDbContext>(
|
||||
builder.Services.AddScoped<IPersistMessageDbContext>(
|
||||
provider =>
|
||||
{
|
||||
var persistMessageDbContext =
|
||||
@ -46,10 +52,10 @@ public static class Extensions
|
||||
return persistMessageDbContext;
|
||||
});
|
||||
|
||||
services.AddScoped<IPersistMessageProcessor, PersistMessageProcessor>();
|
||||
builder.Services.AddScoped<IPersistMessageProcessor, PersistMessageProcessor>();
|
||||
|
||||
services.AddHostedService<PersistMessageBackgroundService>();
|
||||
builder.Services.AddHostedService<PersistMessageBackgroundService>();
|
||||
|
||||
return services;
|
||||
return builder.Services;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
namespace BuildingBlocks.Polly;
|
||||
|
||||
public class CircuitBreakerOptions
|
||||
{
|
||||
public int RetryCount { get; set; }
|
||||
public int BreakDuration { get; set; }
|
||||
}
|
||||
@ -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<T>(this object retrySource, Func<T> action, int retryCount = 3)
|
||||
{
|
||||
var retryPolicy = Policy
|
||||
.Handle<Exception>()
|
||||
.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);
|
||||
}
|
||||
|
||||
@ -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<IConfiguration>().GetOptions<PolicyOptions>(nameof(PolicyOptions));
|
||||
|
||||
Guard.Against.Null(options, nameof(options));
|
||||
|
||||
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger("PollyGrpcCircuitBreakerPoliciesLogger");
|
||||
|
||||
return Policy.HandleResult<HttpResponseMessage>(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");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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<IConfiguration>().GetOptions<PolicyOptions>(nameof(PolicyOptions));
|
||||
|
||||
Guard.Against.Null(options, nameof(options));
|
||||
|
||||
return Policy
|
||||
.HandleResult<HttpResponseMessage>(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<ILoggerFactory>();
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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<IConfiguration>().GetOptions<PolicyOptions>(nameof(PolicyOptions));
|
||||
|
||||
Guard.Against.Null(options, nameof(options));
|
||||
|
||||
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
|
||||
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");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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<IConfiguration>().GetOptions<PolicyOptions>(nameof(PolicyOptions));
|
||||
|
||||
Guard.Against.Null(options, nameof(options));
|
||||
|
||||
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
namespace BuildingBlocks.Polly;
|
||||
|
||||
public class PolicyOptions
|
||||
{
|
||||
public RetryOptions Retry { get; set; }
|
||||
public CircuitBreakerOptions CircuitBreaker { get; set; }
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
namespace BuildingBlocks.Polly;
|
||||
|
||||
public class RetryOptions
|
||||
{
|
||||
public int RetryCount { get; set; }
|
||||
public int SleepDuration { get; set; }
|
||||
}
|
||||
@ -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<IServiceProvider, Task> action)
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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": "*"
|
||||
}
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<GrpcOptions>("Grpc");
|
||||
var resilienceOptions = services.GetOptions<HttpStandardResilienceOptions>(nameof(HttpStandardResilienceOptions));
|
||||
|
||||
services.AddGrpcClient<FlightGrpcService.FlightGrpcServiceClient>(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<PassengerGrpcService.PassengerGrpcServiceClient>(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;
|
||||
}
|
||||
|
||||
@ -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<ICurrentUserProvider, CurrentUserProvider>();
|
||||
builder.Services.AddScoped<IEventMapper, BookingEventMapper>();
|
||||
builder.Services.AddScoped<IEventDispatcher, EventDispatcher>();
|
||||
@ -44,25 +61,10 @@ public static class InfrastructureExtensions
|
||||
|
||||
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
||||
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(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<BookingReadDbContext>();
|
||||
|
||||
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<AuthHeaderHandler>();
|
||||
|
||||
// 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<AppOptions>(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())
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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": "*"
|
||||
}
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<ICurrentUserProvider, CurrentUserProvider>();
|
||||
builder.Services.AddScoped<IEventMapper, FlightEventMapper>();
|
||||
|
||||
@ -50,27 +67,12 @@ public static class InfrastructureExtensions
|
||||
var appOptions = builder.Services.GetOptions<AppOptions>(nameof(AppOptions));
|
||||
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
||||
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(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<FlightDbContext>();
|
||||
builder.AddCustomDbContext<FlightDbContext>(nameof(Flight));
|
||||
builder.Services.AddScoped<IDataSeeder, FlightDataSeeder>();
|
||||
builder.AddMongoDbContext<FlightReadDbContext>();
|
||||
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<AppOptions>(nameof(AppOptions));
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseCustomHealthCheck();
|
||||
app.UseCustomObservability();
|
||||
|
||||
app.UseCustomProblemDetails();
|
||||
app.UseSerilogRequestLogging(options =>
|
||||
{
|
||||
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
||||
});
|
||||
app.UseCorrelationId();
|
||||
app.UseMigration<FlightDbContext>();
|
||||
app.UseCustomHealthCheck();
|
||||
app.MapGrpcService<FlightGrpcServices>();
|
||||
app.UseRateLimiter();
|
||||
|
||||
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
||||
|
||||
if (env.IsDevelopment())
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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": "*"
|
||||
}
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}
|
||||
@ -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<ICurrentUserProvider, CurrentUserProvider>();
|
||||
builder.Services.AddScoped<IEventMapper, IdentityEventMapper>();
|
||||
builder.Services.AddScoped<IEventDispatcher, EventDispatcher>();
|
||||
@ -45,36 +60,19 @@ public static class InfrastructureExtensions
|
||||
var appOptions = builder.Services.GetOptions<AppOptions>(nameof(AppOptions));
|
||||
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
||||
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(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<IdentityContext>();
|
||||
builder.AddPersistMessageProcessor();
|
||||
builder.AddCustomDbContext<IdentityContext>(nameof(Identity));
|
||||
builder.Services.AddScoped<IDataSeeder, IdentityDataSeeder>();
|
||||
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<AppOptions>(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<IdentityContext>();
|
||||
app.UseCustomHealthCheck();
|
||||
app.UseIdentityServer();
|
||||
|
||||
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.TestBase;
|
||||
using FluentAssertions;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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": "*"
|
||||
}
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<ICurrentUserProvider, CurrentUserProvider>();
|
||||
builder.Services.AddScoped<IEventMapper, PassengerEventMapper>();
|
||||
builder.Services.AddScoped<IEventDispatcher, EventDispatcher>();
|
||||
@ -46,28 +64,10 @@ public static class InfrastructureExtensions
|
||||
var appOptions = builder.Services.GetOptions<AppOptions>(nameof(AppOptions));
|
||||
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
||||
|
||||
builder.Services.AddRateLimiter(
|
||||
options =>
|
||||
{
|
||||
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(
|
||||
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<PassengerDbContext>();
|
||||
builder.AddPersistMessageProcessor(nameof(PersistMessage));
|
||||
builder.AddCustomDbContext<PassengerDbContext>(nameof(Passenger));
|
||||
builder.AddMongoDbContext<PassengerReadDbContext>();
|
||||
|
||||
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<AppOptions>(nameof(AppOptions));
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseCustomHealthCheck();
|
||||
app.UseCustomObservability();
|
||||
|
||||
app.UseCustomProblemDetails();
|
||||
|
||||
app.UseSerilogRequestLogging(
|
||||
options =>
|
||||
{
|
||||
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
||||
});
|
||||
|
||||
app.UseCorrelationId();
|
||||
app.UseMigration<PassengerDbContext>();
|
||||
app.UseCustomHealthCheck();
|
||||
app.MapGrpcService<PassengerGrpcServices>();
|
||||
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user