Skip to content

Commit

Permalink
feat: Aspire (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
alex289 authored Nov 22, 2024
2 parents a265ff7 + f909b61 commit 5e8d6ad
Show file tree
Hide file tree
Showing 24 changed files with 401 additions and 59 deletions.
1 change: 1 addition & 0 deletions CleanArchitecture.Api/CleanArchitecture.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
<ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj" />
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" />
<ProjectReference Include="..\CleanArchitecture.ServiceDefaults\CleanArchitecture.ServiceDefaults.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
41 changes: 41 additions & 0 deletions CleanArchitecture.Api/Extensions/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using CleanArchitecture.Domain.Rabbitmq;
using Microsoft.Extensions.Configuration;

namespace CleanArchitecture.Api.Extensions;

public static class ConfigurationExtensions
{
public static RabbitMqConfiguration GetRabbitMqConfiguration(
this IConfiguration configuration)
{
var isAspire = configuration["ASPIRE_ENABLED"] == "true";

var rabbitEnabled = configuration["RabbitMQ:Enabled"];
var rabbitHost = configuration["RabbitMQ:Host"];
var rabbitPort = configuration["RabbitMQ:Port"];
var rabbitUser = configuration["RabbitMQ:Username"];
var rabbitPass = configuration["RabbitMQ:Password"];

if (isAspire)
{
rabbitEnabled = "true";
var connectionString = configuration["ConnectionStrings:RabbitMq"];

var rabbitUri = new Uri(connectionString!);
rabbitHost = rabbitUri.Host;
rabbitPort = rabbitUri.Port.ToString();
rabbitUser = rabbitUri.UserInfo.Split(':')[0];
rabbitPass = rabbitUri.UserInfo.Split(':')[1];
}

return new RabbitMqConfiguration()
{
Host = rabbitHost ?? "",
Port = int.Parse(rabbitPort ?? "0"),
Enabled = bool.Parse(rabbitEnabled ?? "false"),
Username = rabbitUser ?? "",
Password = rabbitPass ?? ""
};
}
}
36 changes: 22 additions & 14 deletions CleanArchitecture.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@
using CleanArchitecture.Domain.Rabbitmq.Extensions;
using CleanArchitecture.Infrastructure.Database;
using CleanArchitecture.Infrastructure.Extensions;
using CleanArchitecture.ServiceDefaults;
using HealthChecks.ApplicationStatus.DependencyInjection;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

builder.Services.AddControllers();
builder.Services.AddGrpc();
builder.Services.AddGrpcReflection();
Expand All @@ -28,40 +30,44 @@
.AddDbContextCheck<ApplicationDbContext>()
.AddApplicationStatus();

var isAspire = builder.Configuration["ASPIRE_ENABLED"] == "true";

var rabbitConfiguration = builder.Configuration.GetRabbitMqConfiguration();
var redisConnectionString =
isAspire ? builder.Configuration["ConnectionStrings:Redis"] : builder.Configuration["RedisHostName"];
var dbConnectionString = isAspire
? builder.Configuration["ConnectionStrings:Database"]
: builder.Configuration["ConnectionStrings:DefaultConnection"];

if (builder.Environment.IsProduction())
{
var rabbitHost = builder.Configuration["RabbitMQ:Host"];
var rabbitPort = builder.Configuration["RabbitMQ:Port"];
var rabbitUser = builder.Configuration["RabbitMQ:Username"];
var rabbitPass = builder.Configuration["RabbitMQ:Password"];

builder.Services
.AddHealthChecks()
.AddSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")!)
.AddRedis(builder.Configuration["RedisHostName"]!, "Redis")
.AddSqlServer(dbConnectionString!)
.AddRedis(redisConnectionString!, "Redis")
.AddRabbitMQ(
$"amqp://{rabbitUser}:{rabbitPass}@{rabbitHost}:{rabbitPort}",
rabbitConfiguration.ConnectionString,
name: "RabbitMQ");
}

builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseLazyLoadingProxies();
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"),
options.UseSqlServer(dbConnectionString,
b => b.MigrationsAssembly("CleanArchitecture.Infrastructure"));
});

builder.Services.AddSwagger();
builder.Services.AddAuth(builder.Configuration);
builder.Services.AddInfrastructure(builder.Configuration, "CleanArchitecture.Infrastructure");
builder.Services.AddInfrastructure("CleanArchitecture.Infrastructure", dbConnectionString!);
builder.Services.AddQueryHandlers();
builder.Services.AddServices();
builder.Services.AddSortProviders();
builder.Services.AddCommandHandlers();
builder.Services.AddNotificationHandlers();
builder.Services.AddApiUser();

builder.Services.AddRabbitMqHandler(builder.Configuration, "RabbitMQ");
builder.Services.AddRabbitMqHandler(rabbitConfiguration);

builder.Services.AddHostedService<SetInactiveUsersService>();

Expand All @@ -73,11 +79,11 @@
console.IncludeScopes = true;
}));

if (builder.Environment.IsProduction() || !string.IsNullOrWhiteSpace(builder.Configuration["RedisHostName"]))
if (builder.Environment.IsProduction() || !string.IsNullOrWhiteSpace(redisConnectionString))
{
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration["RedisHostName"];
options.Configuration = redisConnectionString;
options.InstanceName = "clean-architecture";
});
}
Expand All @@ -88,6 +94,8 @@

var app = builder.Build();

app.MapDefaultEndpoints();

using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
Expand Down
16 changes: 0 additions & 16 deletions CleanArchitecture.Api/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:38452",
"sslPort": 44309
}
},
"profiles": {
"CleanArchitecture.Api": {
"commandName": "Project",
Expand All @@ -18,14 +10,6 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
25 changes: 25 additions & 0 deletions CleanArchitecture.AppHost/CleanArchitecture.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>e7ec3788-69e9-4631-b350-d59657ddd747</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.RabbitMQ" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.SqlServer" Version="9.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Api\CleanArchitecture.Api.csproj" />
</ItemGroup>

</Project>
26 changes: 26 additions & 0 deletions CleanArchitecture.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
var builder = DistributedApplication.CreateBuilder(args);

var redis = builder.AddRedis("Redis").WithRedisInsight();

var rabbitPasswordRessource = new ParameterResource("password", _ => "guest");
var rabbitPasswordParameter =
builder.AddParameter("username", rabbitPasswordRessource.Value);

var rabbitMq = builder
.AddRabbitMQ("RabbitMq", null, rabbitPasswordParameter, 5672)
.WithManagementPlugin();

var sqlServer = builder.AddSqlServer("SqlServer");
var db = sqlServer.AddDatabase("Database", "clean-architecture");

builder.AddProject<Projects.CleanArchitecture_Api>("CleanArchitecture-Api")
.WithOtlpExporter()
.WithHttpHealthCheck("/health")
.WithReference(redis)
.WaitFor(redis)
.WithReference(rabbitMq)
.WaitFor(rabbitMq)
.WithReference(db)
.WaitFor(sqlServer);

builder.Build().Run();
18 changes: 18 additions & 0 deletions CleanArchitecture.AppHost/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"Aspire": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17270;http://localhost:15188",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21200",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22111",
"ASPIRE_ENABLED": "true"
}
}
}
}
8 changes: 8 additions & 0 deletions CleanArchitecture.AppHost/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions CleanArchitecture.AppHost/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace CleanArchitecture.Domain.Rabbitmq.Extensions;
Expand All @@ -7,12 +6,9 @@ public static class ServiceCollectionExtensions
{
public static IServiceCollection AddRabbitMqHandler(
this IServiceCollection services,
IConfiguration configuration,
string rabbitMqConfigSection)
RabbitMqConfiguration configuration)
{
var rabbitMq = new RabbitMqConfiguration();
configuration.Bind(rabbitMqConfigSection, rabbitMq);
services.AddSingleton(rabbitMq);
services.AddSingleton(configuration);

services.AddSingleton<RabbitMqHandler>();
services.AddHostedService(serviceProvider => serviceProvider.GetService<RabbitMqHandler>()!);
Expand Down
2 changes: 2 additions & 0 deletions CleanArchitecture.Domain/Rabbitmq/RabbitMqConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ public sealed class RabbitMqConfiguration
public bool Enabled { get; set; }
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;

public string ConnectionString => $"amqp://{Username}:{Password}@{Host}:{Port}";
}
13 changes: 9 additions & 4 deletions CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ namespace CleanArchitecture.Domain.Rabbitmq;

public sealed class RabbitMqHandler : BackgroundService
{
private IChannel? _channel;
private readonly RabbitMqConfiguration _configuration;

private readonly ConcurrentDictionary<string, List<ConsumeEventHandler>> _consumers = new();

private readonly ILogger<RabbitMqHandler> _logger;

private readonly ConcurrentQueue<IRabbitMqAction> _pendingActions = new();
private IChannel? _channel;

public RabbitMqHandler(
RabbitMqConfiguration configuration,
Expand All @@ -38,17 +38,21 @@ public override async Task StartAsync(CancellationToken cancellationToken)
return;
}

_logger.LogInformation("Starting RabbitMQ connection");

var factory = new ConnectionFactory
{
AutomaticRecoveryEnabled = true,
HostName = _configuration.Host,
Port = _configuration.Port,
UserName = _configuration.Username,
Password = _configuration.Password,
Password = _configuration.Password
};

var connection = await factory.CreateConnectionAsync(cancellationToken);
_channel = await connection.CreateChannelAsync(null, cancellationToken);

await base.StartAsync(cancellationToken);
}


Expand Down Expand Up @@ -129,14 +133,15 @@ public void AddExchangeConsumer(string exchange, string queue, ConsumeEventHandl
AddExchangeConsumer(exchange, string.Empty, queue, consumer);
}

private async Task AddEventConsumer(string exchange, string queueName, string routingKey, ConsumeEventHandler consumer)
private async Task AddEventConsumer(string exchange, string queueName, string routingKey,
ConsumeEventHandler consumer)
{
if (!_configuration.Enabled)
{
_logger.LogInformation("RabbitMQ is disabled. Event consumer will not be added.");
return;
}

var key = $"{exchange}-{routingKey}";

if (!_consumers.TryGetValue(key, out var consumers))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using CleanArchitecture.Infrastructure.Repositories;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace CleanArchitecture.Infrastructure.Extensions;
Expand All @@ -16,24 +15,23 @@ public static class ServiceCollectionExtensions
{
public static IServiceCollection AddInfrastructure(
this IServiceCollection services,
IConfiguration configuration,
string migrationsAssemblyName,
string connectionStringName = "DefaultConnection")
string connectionString)
{
// Add event store db context
services.AddDbContext<EventStoreDbContext>(
options =>
{
options.UseSqlServer(
configuration.GetConnectionString(connectionStringName),
connectionString,
b => b.MigrationsAssembly(migrationsAssemblyName));
});

services.AddDbContext<DomainNotificationStoreDbContext>(
options =>
{
options.UseSqlServer(
configuration.GetConnectionString(connectionStringName),
connectionString,
b => b.MigrationsAssembly(migrationsAssemblyName));
});

Expand Down
Loading

0 comments on commit 5e8d6ad

Please sign in to comment.