0
0
mirror of https://github.com/alex289/CleanArchitecture.git synced 2025-06-29 18:21:08 +00:00

feat: Aspire (#80)

This commit is contained in:
Alex 2024-11-22 16:26:11 +01:00 committed by GitHub
commit 5e8d6ad45f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 401 additions and 59 deletions

View File

@ -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>

View File

@ -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 ?? ""
};
}
}

View File

@ -6,18 +6,20 @@ using CleanArchitecture.Domain.Extensions;
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();
@ -28,32 +30,36 @@ builder.Services
.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();
@ -61,7 +67,7 @@ builder.Services.AddCommandHandlers();
builder.Services.AddNotificationHandlers();
builder.Services.AddApiUser();
builder.Services.AddRabbitMqHandler(builder.Configuration, "RabbitMQ");
builder.Services.AddRabbitMqHandler(rabbitConfiguration);
builder.Services.AddHostedService<SetInactiveUsersService>();
@ -73,11 +79,11 @@ builder.Services.AddLogging(x => x.AddSimpleConsole(console =>
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";
});
}
@ -88,6 +94,8 @@ else
var app = builder.Build();
app.MapDefaultEndpoints();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;

View File

@ -1,13 +1,5 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:38452",
"sslPort": 44309
}
},
"profiles": {
"CleanArchitecture.Api": {
"commandName": "Project",
@ -18,14 +10,6 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -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>

View File

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

View File

@ -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"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}

View File

@ -1,4 +1,3 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace CleanArchitecture.Domain.Rabbitmq.Extensions;
@ -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>()!);

View File

@ -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}";
}

View File

@ -13,7 +13,6 @@ namespace CleanArchitecture.Domain.Rabbitmq;
public sealed class RabbitMqHandler : BackgroundService
{
private IChannel? _channel;
private readonly RabbitMqConfiguration _configuration;
private readonly ConcurrentDictionary<string, List<ConsumeEventHandler>> _consumers = new();
@ -21,6 +20,7 @@ public sealed class RabbitMqHandler : BackgroundService
private readonly ILogger<RabbitMqHandler> _logger;
private readonly ConcurrentQueue<IRabbitMqAction> _pendingActions = new();
private IChannel? _channel;
public RabbitMqHandler(
RabbitMqConfiguration configuration,
@ -38,17 +38,21 @@ public sealed class RabbitMqHandler : BackgroundService
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);
}
@ -129,14 +133,15 @@ public sealed class RabbitMqHandler : BackgroundService
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))

View File

@ -7,7 +7,6 @@ using CleanArchitecture.Infrastructure.EventSourcing;
using CleanArchitecture.Infrastructure.Repositories;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace CleanArchitecture.Infrastructure.Extensions;
@ -16,16 +15,15 @@ public static class ServiceCollectionExtensions
{
public static IServiceCollection AddInfrastructure(
this IServiceCollection services,
IConfiguration configuration,
string migrationsAssemblyName,
string connectionStringName = "DefaultConnection")
string connectionString)
{
// Add event store db context
services.AddDbContext<EventStoreDbContext>(
options =>
{
options.UseSqlServer(
configuration.GetConnectionString(connectionStringName),
connectionString,
b => b.MigrationsAssembly(migrationsAssemblyName));
});
@ -33,7 +31,7 @@ public static class ServiceCollectionExtensions
options =>
{
options.UseSqlServer(
configuration.GetConnectionString(connectionStringName),
connectionString,
b => b.MigrationsAssembly(migrationsAssemblyName));
});

View File

@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.3.0">
<PackageReference Include="NUnit.Analyzers" Version="4.4.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
using CleanArchitecture.Domain.Entities;
using CleanArchitecture.Infrastructure.Database;
using CleanArchitecture.IntegrationTests.Fixtures;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
namespace CleanArchitecture.IntegrationTests.ExternalServices;
public sealed class RedisTestFixture : TestFixtureBase
{
public Guid CreatedTenantId { get; } = Guid.NewGuid();
public IDistributedCache DistributedCache { get; }
public RedisTestFixture()
{
DistributedCache = Factory.Services.GetRequiredService<IDistributedCache>();
}
public async Task SeedTestData()
{
await GlobalSetupFixture.RespawnDatabaseAsync();
using var context = Factory.Services.GetRequiredService<ApplicationDbContext>();
context.Tenants.Add(new Tenant(
CreatedTenantId,
"Test Tenant"));
await context.SaveChangesAsync();
}
}

View File

@ -0,0 +1,34 @@
using System.Threading.Tasks;
using CleanArchitecture.Application.ViewModels.Tenants;
using CleanArchitecture.Domain;
using CleanArchitecture.Domain.Entities;
using CleanArchitecture.IntegrationTests.Extensions;
using FluentAssertions;
using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
namespace CleanArchitecture.IntegrationTests.ExternalServices;
public sealed class RedisTests
{
private readonly RedisTestFixture _fixture = new();
[OneTimeSetUp]
public async Task Setup() => await _fixture.SeedTestData();
[Test, Order(0)]
public async Task Should_Get_Tenant_By_Id_And_Ensure_Cache()
{
var response = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}");
var message = await response.Content.ReadAsJsonAsync<TenantViewModel>();
message!.Data!.Id.Should().Be(_fixture.CreatedTenantId);
var json = await _fixture.DistributedCache.GetStringAsync(CacheKeyGenerator.GetEntityCacheKey<Tenant>(_fixture.CreatedTenantId));
json.Should().NotBeNullOrEmpty();
var tenant = JsonConvert.DeserializeObject<TenantViewModel>(json!)!;
tenant.Should().NotBeNull();
tenant.Id.Should().Be(_fixture.CreatedTenantId);
}
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireSharedProject>true</IsAspireSharedProject>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.0.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.0.0-beta.12" />
<PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.9.0-beta.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,101 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ServiceDiscovery;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
namespace CleanArchitecture.ServiceDefaults;
public static class Extensions
{
private const string AspireEnabled = "ASPIRE_ENABLED";
public static void AddServiceDefaults<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
{
if (builder.Configuration[AspireEnabled] != "true")
{
return;
}
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
http.AddStandardResilienceHandler();
http.AddServiceDiscovery();
});
builder.Services.Configure<ServiceDiscoveryOptions>(options => { options.AllowedSchemes = ["https"]; });
}
private static void ConfigureOpenTelemetry<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
{
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
.WithTracing(tracing =>
{
tracing.AddSource(builder.Environment.ApplicationName)
.AddAspNetCoreInstrumentation()
.AddGrpcClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddHttpClientInstrumentation();
});
builder.AddOpenTelemetryExporters();
}
private static void AddOpenTelemetryExporters<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
if (useOtlpExporter)
{
builder.Services.AddOpenTelemetry().UseOtlpExporter();
}
}
private static void AddDefaultHealthChecks<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
{
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
}
public static void MapDefaultEndpoints(this WebApplication app)
{
if (app.Configuration[AspireEnabled] != "true")
{
return;
}
if (app.Environment.IsDevelopment())
{
app.MapHealthChecks("/health");
app.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
});
}
}
}

View File

@ -29,6 +29,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D3DF9DF5
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Shared", "CleanArchitecture.Shared\CleanArchitecture.Shared.csproj", "{E82B473D-0281-4713-9550-7D3FF7D9CFDE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.AppHost", "CleanArchitecture.AppHost\CleanArchitecture.AppHost.csproj", "{AF8AC381-9A62-49A8-B42D-44BF8B0F28D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.ServiceDefaults", "CleanArchitecture.ServiceDefaults\CleanArchitecture.ServiceDefaults.csproj", "{CED4C7AC-AD5C-4054-A338-95C32945D69E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aspire", "Aspire", "{53D849CC-87DF-4A90-88C1-8380A8C07CB0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -83,6 +89,14 @@ Global
{E82B473D-0281-4713-9550-7D3FF7D9CFDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E82B473D-0281-4713-9550-7D3FF7D9CFDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E82B473D-0281-4713-9550-7D3FF7D9CFDE}.Release|Any CPU.Build.0 = Release|Any CPU
{AF8AC381-9A62-49A8-B42D-44BF8B0F28D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF8AC381-9A62-49A8-B42D-44BF8B0F28D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF8AC381-9A62-49A8-B42D-44BF8B0F28D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF8AC381-9A62-49A8-B42D-44BF8B0F28D0}.Release|Any CPU.Build.0 = Release|Any CPU
{CED4C7AC-AD5C-4054-A338-95C32945D69E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CED4C7AC-AD5C-4054-A338-95C32945D69E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CED4C7AC-AD5C-4054-A338-95C32945D69E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CED4C7AC-AD5C-4054-A338-95C32945D69E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -93,6 +107,8 @@ Global
{EC8122EB-C5E0-452F-B1CE-DA47DAEBC8F2} = {D3DF9DF5-BD7D-48BC-8BE6-DBD0FABB8B45}
{39732BD4-909F-410C-8737-1F9FE3E269A7} = {D3DF9DF5-BD7D-48BC-8BE6-DBD0FABB8B45}
{E3A836DD-85DB-44FD-BC19-DDFE111D9EB0} = {D3DF9DF5-BD7D-48BC-8BE6-DBD0FABB8B45}
{AF8AC381-9A62-49A8-B42D-44BF8B0F28D0} = {53D849CC-87DF-4A90-88C1-8380A8C07CB0}
{CED4C7AC-AD5C-4054-A338-95C32945D69E} = {53D849CC-87DF-4A90-88C1-8380A8C07CB0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DDAAEEA0-FB1B-4EAD-902B-C12034FFC17A}

View File

@ -25,23 +25,29 @@ The project uses the following dependencies:
- **gRPC**: gRPC is an open-source remote procedure call framework that enables efficient communication between distributed systems using a variety of programming languages and protocols.
## Running the Project
To run the project, follow these steps:
To run the project, follow these steps:
1. Clone the repository to your local machine.
2. Open the solution in your IDE of choice.
3. Build the solution to restore the dependencies.
4. Update the connection string in the appsettings.json file to point to your database.
5. Start the API project
5. Start the API project (Alterntively you can use the `dotnet run --project CleanArchitecture.Api` command)
6. The database migrations will be automatically applied on start-up. If the database does not exist, it will be created.
7. The API should be accessible at `https://localhost:<port>/api/<controller>` where `<port>` is the port number specified in the project properties and `<controller>` is the name of the API controller.
### Using Aspire
1. Run `dotnet run --project CleanArchitecture.AppHost` in the root directory of the project.
### Using docker
Requirements
> This is only needed if running the API locally or only the docker image
1. Redis: `docker run --name redis -d -p 6379:6379 -e ALLOW_EMPTY_PASSWORD=yes redis:latest`
2. Add this to the redis configuration in the Program.cs
1. SqlServer: `docker run --name sqlserver -d -p 1433:1433 -e ACCEPT_EULA=Y -e SA_PASSWORD='Password123!#' mcr.microsoft.com/mssql/server`
1. RabbitMq: `docker run --name rabbitmq -d -p 5672:5672 -p 15672:15672 rabbitmq:4-management`
3. Redis: `docker run --name redis -d -p 6379:6379 -e ALLOW_EMPTY_PASSWORD=yes redis:latest`
4. Add this to the redis configuration in the Program.cs
```csharp
options.ConfigurationOptions = new ConfigurationOptions
{
@ -49,11 +55,10 @@ options.ConfigurationOptions = new ConfigurationOptions
EndPoints = { "localhost", "6379" }
};
```
3. RabbitMq: `docker run --name rabbitmq -d -p 5672:5672 -p 15672:15672 rabbitmq:3-management`
Running the container
1. Build the Dockerfile: `docker build -t clean-architecture .`
2. Run the Container: `docker run -p 80:80 clean-architecture`
2. Run the Container: `docker run --name clean-architecture -d -p 80:80 -p 8080:8080 clean-architecture`
### Using docker-compose

View File

@ -1,4 +1,3 @@
version: "3"
services:
app:
build:
@ -37,7 +36,7 @@ services:
- 1433:1433
redis:
image: docker.io/bitnami/redis:7.2
image: redis:latest
environment:
# ALLOW_EMPTY_PASSWORD is recommended only for development.
- ALLOW_EMPTY_PASSWORD=yes
@ -48,7 +47,7 @@ services:
- 'redis_data:/bitnami/redis/data'
rabbitmq:
image: "rabbitmq:3-management"
image: "rabbitmq:4-management"
ports:
- 5672:5672
- 15672:15672

View File

@ -6,9 +6,14 @@ spec:
selector:
app: clean-architecture-app
ports:
- protocol: TCP
- name: http
protocol: TCP
port: 80
targetPort: 80
- name: grpc
protocol: TCP
port: 8080
targetPort: 8080
type: LoadBalancer
---
@ -32,9 +37,12 @@ spec:
image: alexdev28/clean-architecture:latest
ports:
- containerPort: 80
protocol: TCP
- containerPort: 8080
protocol: TCP
env:
- name: ASPNETCORE_HTTP_PORTS
value: 80
value: "80"
- name: Kestrel__Endpoints__Http__Url
value: http://+:80
- name: Kestrel__Endpoints__Grpc__Url

View File

@ -32,7 +32,7 @@ spec:
spec:
containers:
- name: rabbitmq
image: rabbitmq:management
image: rabbitmq:4-management
ports:
- containerPort: 5672
- containerPort: 15672

View File

@ -27,7 +27,7 @@ spec:
spec:
containers:
- name: redis
image: docker.io/bitnami/redis:7.2
image: redis:latest
env:
# ALLOW_EMPTY_PASSWORD is recommended only for development.
- name: ALLOW_EMPTY_PASSWORD