diff --git a/CleanArchitecture.Api/BackgroundServices/SetInactiveUsersService.cs b/CleanArchitecture.Api/BackgroundServices/SetInactiveUsersService.cs index 8c94c7a..f63e0f8 100644 --- a/CleanArchitecture.Api/BackgroundServices/SetInactiveUsersService.cs +++ b/CleanArchitecture.Api/BackgroundServices/SetInactiveUsersService.cs @@ -15,8 +15,8 @@ namespace CleanArchitecture.Api.BackgroundServices; public sealed class SetInactiveUsersService : BackgroundService { - private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; public SetInactiveUsersService( IServiceProvider serviceProvider, @@ -55,7 +55,7 @@ public sealed class SetInactiveUsersService : BackgroundService { user.SetInactive(); } - + try { await context.SaveChangesAsync(stoppingToken); @@ -64,7 +64,7 @@ public sealed class SetInactiveUsersService : BackgroundService { _logger.LogError(ex, "Error while setting users to inactive"); } - + await Task.Delay(TimeSpan.FromDays(1), stoppingToken); } } diff --git a/CleanArchitecture.Api/CleanArchitecture.Api.csproj b/CleanArchitecture.Api/CleanArchitecture.Api.csproj index 5b5b96b..2a4b57b 100644 --- a/CleanArchitecture.Api/CleanArchitecture.Api.csproj +++ b/CleanArchitecture.Api/CleanArchitecture.Api.csproj @@ -7,28 +7,29 @@ - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + - - - - + + + + diff --git a/CleanArchitecture.Api/Program.cs b/CleanArchitecture.Api/Program.cs index 4036e29..5448b13 100644 --- a/CleanArchitecture.Api/Program.cs +++ b/CleanArchitecture.Api/Program.cs @@ -3,6 +3,7 @@ using CleanArchitecture.Api.Extensions; using CleanArchitecture.Application.Extensions; using CleanArchitecture.Application.gRPC; using CleanArchitecture.Domain.Extensions; +using CleanArchitecture.Domain.Rabbitmq.Extensions; using CleanArchitecture.Infrastructure.Database; using CleanArchitecture.Infrastructure.Extensions; using HealthChecks.ApplicationStatus.DependencyInjection; @@ -14,7 +15,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using StackExchange.Redis; var builder = WebApplication.CreateBuilder(args); @@ -29,10 +29,17 @@ builder.Services if (builder.Environment.IsProduction()) { + var rabbitHost = builder.Configuration["RabbitMQ:Host"]; + 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"); + .AddRedis(builder.Configuration["RedisHostName"]!, "Redis") + .AddRabbitMQ( + rabbitConnectionString: $"amqp://{rabbitUser}:{rabbitPass}@{rabbitHost}", + name: "RabbitMQ"); } builder.Services.AddDbContext(options => @@ -51,6 +58,8 @@ builder.Services.AddCommandHandlers(); builder.Services.AddNotificationHandlers(); builder.Services.AddApiUser(); +builder.Services.AddRabbitMqHandler(builder.Configuration, "RabbitMQ"); + builder.Services.AddHostedService(); builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblies(typeof(Program).Assembly); }); diff --git a/CleanArchitecture.Api/appsettings.Development.json b/CleanArchitecture.Api/appsettings.Development.json index 436f0c9..3506cff 100644 --- a/CleanArchitecture.Api/appsettings.Development.json +++ b/CleanArchitecture.Api/appsettings.Development.json @@ -12,5 +12,11 @@ "Issuer": "CleanArchitectureServer", "Audience": "CleanArchitectureClient", "Secret": "sD3v061gf8BxXgmxcHssasjdlkasjd87439284)@#(*" + }, + "RabbitMQ": { + "Host": "localhost", + "Username": "guest", + "Password": "guest", + "Enabled": "True" } } diff --git a/CleanArchitecture.Api/appsettings.Integration.json b/CleanArchitecture.Api/appsettings.Integration.json new file mode 100644 index 0000000..1913b40 --- /dev/null +++ b/CleanArchitecture.Api/appsettings.Integration.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "RabbitMQ": { + "Host": "localhost", + "Username": "guest", + "Password": "guest", + "Enabled": "False" + } +} diff --git a/CleanArchitecture.Api/appsettings.json b/CleanArchitecture.Api/appsettings.json index 6ea892f..e130b52 100644 --- a/CleanArchitecture.Api/appsettings.json +++ b/CleanArchitecture.Api/appsettings.json @@ -14,5 +14,11 @@ "Audience": "CleanArchitectureClient", "Secret": "sD3v061gf8BxXgmxcHssasjdlkasjd87439284)@#(*" }, - "RedisHostName": "redis" + "RedisHostName": "redis", + "RabbitMQ": { + "Host": "rabbitmq", + "Username": "admin", + "Password": "DOIA9234JF", + "Enabled": "True" + } } diff --git a/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj b/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj index 5351867..ddb3c48 100644 --- a/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj +++ b/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj @@ -8,11 +8,11 @@ - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -24,8 +24,8 @@ - - + + diff --git a/CleanArchitecture.Application.Tests/Queries/Tenants/GetAllTenantsQueryHandlerTests.cs b/CleanArchitecture.Application.Tests/Queries/Tenants/GetAllTenantsQueryHandlerTests.cs index 061cf4b..4747660 100644 --- a/CleanArchitecture.Application.Tests/Queries/Tenants/GetAllTenantsQueryHandlerTests.cs +++ b/CleanArchitecture.Application.Tests/Queries/Tenants/GetAllTenantsQueryHandlerTests.cs @@ -32,7 +32,7 @@ public sealed class GetAllTenantsQueryHandlerTests result.PageSize.Should().Be(query.PageSize); result.Page.Should().Be(query.Page); result.Count.Should().Be(1); - + tenant.Should().BeEquivalentTo(result.Items.First()); } @@ -40,7 +40,7 @@ public sealed class GetAllTenantsQueryHandlerTests public async Task Should_Not_Get_Deleted_Tenant() { _fixture.SetupTenant(true); - + var query = new PageQuery { PageSize = 10, @@ -50,7 +50,7 @@ public sealed class GetAllTenantsQueryHandlerTests var result = await _fixture.QueryHandler.Handle( new GetAllTenantsQuery(query), default); - + result.PageSize.Should().Be(query.PageSize); result.Page.Should().Be(query.Page); result.Count.Should().Be(0); diff --git a/CleanArchitecture.Application.Tests/Queries/Users/GetAllUsersQueryHandlerTests.cs b/CleanArchitecture.Application.Tests/Queries/Users/GetAllUsersQueryHandlerTests.cs index 60ccfc0..2a03ced 100644 --- a/CleanArchitecture.Application.Tests/Queries/Users/GetAllUsersQueryHandlerTests.cs +++ b/CleanArchitecture.Application.Tests/Queries/Users/GetAllUsersQueryHandlerTests.cs @@ -16,7 +16,7 @@ public sealed class GetAllUsersQueryHandlerTests public async Task Should_Get_All_Users() { var user = _fixture.SetupUserAsync(); - + var query = new PageQuery { PageSize = 1, @@ -28,7 +28,7 @@ public sealed class GetAllUsersQueryHandlerTests default); _fixture.VerifyNoDomainNotification(); - + result.PageSize.Should().Be(query.PageSize); result.Page.Should().Be(query.Page); result.Count.Should().Be(1); @@ -43,7 +43,7 @@ public sealed class GetAllUsersQueryHandlerTests public async Task Should_Not_Get_Deleted_Users() { _fixture.SetupDeletedUserAsync(); - + var query = new PageQuery { PageSize = 10, @@ -55,7 +55,7 @@ public sealed class GetAllUsersQueryHandlerTests default); _fixture.VerifyNoDomainNotification(); - + result.PageSize.Should().Be(query.PageSize); result.Page.Should().Be(query.Page); result.Count.Should().Be(0); diff --git a/CleanArchitecture.Application/Interfaces/IUserService.cs b/CleanArchitecture.Application/Interfaces/IUserService.cs index 49e4f91..1a77271 100644 --- a/CleanArchitecture.Application/Interfaces/IUserService.cs +++ b/CleanArchitecture.Application/Interfaces/IUserService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using CleanArchitecture.Application.ViewModels; using CleanArchitecture.Application.ViewModels.Users; diff --git a/CleanArchitecture.Application/Queries/Tenants/GetAll/GetAllTenantsQueryHandler.cs b/CleanArchitecture.Application/Queries/Tenants/GetAll/GetAllTenantsQueryHandler.cs index 839b4ef..5eb5be0 100644 --- a/CleanArchitecture.Application/Queries/Tenants/GetAll/GetAllTenantsQueryHandler.cs +++ b/CleanArchitecture.Application/Queries/Tenants/GetAll/GetAllTenantsQueryHandler.cs @@ -27,21 +27,21 @@ public sealed class GetAllTenantsQueryHandler : .GetAllNoTracking() .Include(x => x.Users) .Where(x => !x.Deleted); - + if (!string.IsNullOrWhiteSpace(request.SearchTerm)) { - tenantsQuery = tenantsQuery.Where(tenant => + tenantsQuery = tenantsQuery.Where(tenant => tenant.Name.Contains(request.SearchTerm)); } - + var totalCount = await tenantsQuery.CountAsync(cancellationToken); - + var tenants = await tenantsQuery .Skip((request.Query.Page - 1) * request.Query.PageSize) .Take(request.Query.PageSize) .Select(tenant => TenantViewModel.FromTenant(tenant)) .ToListAsync(cancellationToken); - + return new PagedResult( totalCount, tenants, request.Query.Page, request.Query.PageSize); } diff --git a/CleanArchitecture.Application/Queries/Users/GetAll/GetAllUsersQueryHandler.cs b/CleanArchitecture.Application/Queries/Users/GetAll/GetAllUsersQueryHandler.cs index dcdd952..67d4d5c 100644 --- a/CleanArchitecture.Application/Queries/Users/GetAll/GetAllUsersQueryHandler.cs +++ b/CleanArchitecture.Application/Queries/Users/GetAll/GetAllUsersQueryHandler.cs @@ -29,14 +29,14 @@ public sealed class GetAllUsersQueryHandler : if (!string.IsNullOrWhiteSpace(request.SearchTerm)) { - usersQuery = usersQuery.Where(user => + usersQuery = usersQuery.Where(user => user.Email.Contains(request.SearchTerm) || user.FirstName.Contains(request.SearchTerm) || user.LastName.Contains(request.SearchTerm)); } - + var totalCount = await usersQuery.CountAsync(cancellationToken); - + var users = await usersQuery .Skip((request.Query.Page - 1) * request.Query.PageSize) .Take(request.Query.PageSize) diff --git a/CleanArchitecture.Application/Services/TenantService.cs b/CleanArchitecture.Application/Services/TenantService.cs index 44eaa67..7a99f6a 100644 --- a/CleanArchitecture.Application/Services/TenantService.cs +++ b/CleanArchitecture.Application/Services/TenantService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using CleanArchitecture.Application.Interfaces; using CleanArchitecture.Application.Queries.Tenants.GetAll; @@ -61,7 +60,7 @@ public sealed class TenantService : ITenantService SlidingExpiration = TimeSpan.FromDays(3), AbsoluteExpiration = DateTimeOffset.Now.AddDays(30) }); - + return cachedTenant; } diff --git a/CleanArchitecture.Application/Services/UserService.cs b/CleanArchitecture.Application/Services/UserService.cs index 4f4522e..847dcdd 100644 --- a/CleanArchitecture.Application/Services/UserService.cs +++ b/CleanArchitecture.Application/Services/UserService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using CleanArchitecture.Application.Interfaces; using CleanArchitecture.Application.Queries.Users.GetAll; diff --git a/CleanArchitecture.Application/ViewModels/PageQuery.cs b/CleanArchitecture.Application/ViewModels/PageQuery.cs index 89fd16f..1e7186f 100644 --- a/CleanArchitecture.Application/ViewModels/PageQuery.cs +++ b/CleanArchitecture.Application/ViewModels/PageQuery.cs @@ -4,14 +4,15 @@ namespace CleanArchitecture.Application.ViewModels; public sealed class PageQuery { + private int _page = 1; private int _pageSize = 10; + public int PageSize { get => _pageSize; set => _pageSize = Math.Max(0, value); } - private int _page = 1; public int Page { get => _page; diff --git a/CleanArchitecture.Application/ViewModels/PagedResult.cs b/CleanArchitecture.Application/ViewModels/PagedResult.cs index 09fac7d..7989b3f 100644 --- a/CleanArchitecture.Application/ViewModels/PagedResult.cs +++ b/CleanArchitecture.Application/ViewModels/PagedResult.cs @@ -23,7 +23,6 @@ public sealed class PagedResult // used by json deserializer private PagedResult() { - } public static PagedResult Empty() diff --git a/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj b/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj index 664c70b..749e02f 100644 --- a/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj +++ b/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj @@ -8,11 +8,11 @@ - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -24,7 +24,7 @@ - + diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/CreateTenant/CreateTenantCommandHandlerTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/CreateTenant/CreateTenantCommandHandlerTests.cs index 2dce689..0ac8c00 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/CreateTenant/CreateTenantCommandHandlerTests.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/CreateTenant/CreateTenantCommandHandlerTests.cs @@ -1,7 +1,7 @@ using System; using CleanArchitecture.Domain.Commands.Tenants.CreateTenant; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.Tenant; +using CleanArchitecture.Shared.Events.Tenant; using Xunit; namespace CleanArchitecture.Domain.Tests.CommandHandler.Tenant.CreateTenant; diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/DeleteTenant/DeleteTenantCommandHandlerTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/DeleteTenant/DeleteTenantCommandHandlerTests.cs index 8f0d378..68134d9 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/DeleteTenant/DeleteTenantCommandHandlerTests.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/DeleteTenant/DeleteTenantCommandHandlerTests.cs @@ -1,7 +1,7 @@ using System; using CleanArchitecture.Domain.Commands.Tenants.DeleteTenant; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.Tenant; +using CleanArchitecture.Shared.Events.Tenant; using Xunit; namespace CleanArchitecture.Domain.Tests.CommandHandler.Tenant.DeleteTenant; diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/UpdateTenant/UpdateTenantCommandHandlerTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/UpdateTenant/UpdateTenantCommandHandlerTests.cs index 77a186a..980b97b 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/UpdateTenant/UpdateTenantCommandHandlerTests.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandler/Tenant/UpdateTenant/UpdateTenantCommandHandlerTests.cs @@ -1,7 +1,7 @@ using System; using CleanArchitecture.Domain.Commands.Tenants.UpdateTenant; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.Tenant; +using CleanArchitecture.Shared.Events.Tenant; using Xunit; namespace CleanArchitecture.Domain.Tests.CommandHandler.Tenant.UpdateTenant; diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandHandlerTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandHandlerTests.cs index 293c603..a318a0f 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandHandlerTests.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandHandlerTests.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using CleanArchitecture.Domain.Commands.Users.ChangePassword; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.User; +using CleanArchitecture.Shared.Events.User; using Xunit; namespace CleanArchitecture.Domain.Tests.CommandHandler.User.ChangePassword; diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandHandlerTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandHandlerTests.cs index 0f0e7d6..ef81bc5 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandHandlerTests.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandHandlerTests.cs @@ -2,7 +2,7 @@ using System; using CleanArchitecture.Domain.Commands.Users.CreateUser; using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.User; +using CleanArchitecture.Shared.Events.User; using NSubstitute; using Xunit; @@ -61,7 +61,7 @@ public sealed class CreateUserCommandHandlerTests DomainErrorCodes.User.UserAlreadyExists, $"There is already a user with Id {command.UserId}"); } - + [Fact] public void Should_Not_Create_Already_Existing_Email() { diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/DeleteUser/DeleteUserCommandHandlerTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/DeleteUser/DeleteUserCommandHandlerTests.cs index 95d2569..7dc1c8d 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandler/User/DeleteUser/DeleteUserCommandHandlerTests.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/DeleteUser/DeleteUserCommandHandlerTests.cs @@ -1,7 +1,7 @@ using System; using CleanArchitecture.Domain.Commands.Users.DeleteUser; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.User; +using CleanArchitecture.Shared.Events.User; using Xunit; namespace CleanArchitecture.Domain.Tests.CommandHandler.User.DeleteUser; @@ -42,7 +42,7 @@ public sealed class DeleteUserCommandHandlerTests ErrorCodes.ObjectNotFound, $"There is no user with Id {command.UserId}"); } - + [Fact] public void Should_Not_Delete_User_Insufficient_Permissions() { diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/DeleteUser/DeleteUserCommandTestFixture.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/DeleteUser/DeleteUserCommandTestFixture.cs index bdd73f9..ed6d653 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandler/User/DeleteUser/DeleteUserCommandTestFixture.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/DeleteUser/DeleteUserCommandTestFixture.cs @@ -40,7 +40,7 @@ public sealed class DeleteUserCommandTestFixture : CommandHandlerFixtureBase return user; } - + public void SetupCurrentUser() { User.GetUserRole().Returns(UserRole.User); diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/UpdateUser/UpdateUserCommandHandlerTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/UpdateUser/UpdateUserCommandHandlerTests.cs index c1229b7..9395f57 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandler/User/UpdateUser/UpdateUserCommandHandlerTests.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/UpdateUser/UpdateUserCommandHandlerTests.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using CleanArchitecture.Domain.Commands.Users.UpdateUser; using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.User; +using CleanArchitecture.Shared.Events.User; using NSubstitute; using Xunit; diff --git a/CleanArchitecture.Domain.Tests/CommandHandlerFixtureBase.cs b/CleanArchitecture.Domain.Tests/CommandHandlerFixtureBase.cs index 1d5087b..a16e775 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandlerFixtureBase.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandlerFixtureBase.cs @@ -1,9 +1,9 @@ using System; using System.Linq.Expressions; -using CleanArchitecture.Domain.DomainEvents; using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Interfaces; using CleanArchitecture.Domain.Notifications; +using CleanArchitecture.Shared.Events; using NSubstitute; namespace CleanArchitecture.Domain.Tests; diff --git a/CleanArchitecture.Domain/CacheKeyGenerator.cs b/CleanArchitecture.Domain/CacheKeyGenerator.cs index aa99aea..11b0bc7 100644 --- a/CleanArchitecture.Domain/CacheKeyGenerator.cs +++ b/CleanArchitecture.Domain/CacheKeyGenerator.cs @@ -5,9 +5,13 @@ namespace CleanArchitecture.Domain; public static class CacheKeyGenerator { - public static string GetEntityCacheKey(TEntity entity) where TEntity : Entity => - $"{typeof(TEntity)}-{entity.Id}"; + public static string GetEntityCacheKey(TEntity entity) where TEntity : Entity + { + return $"{typeof(TEntity)}-{entity.Id}"; + } - public static string GetEntityCacheKey(Guid id) where TEntity : Entity => - $"{typeof(TEntity)}-{id}"; + public static string GetEntityCacheKey(Guid id) where TEntity : Entity + { + return $"{typeof(TEntity)}-{id}"; + } } \ No newline at end of file diff --git a/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj b/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj index e192c77..6055242 100644 --- a/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj +++ b/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj @@ -10,11 +10,16 @@ - + + - + + + + + diff --git a/CleanArchitecture.Domain/Commands/Tenants/CreateTenant/CreateTenantCommandHandler.cs b/CleanArchitecture.Domain/Commands/Tenants/CreateTenant/CreateTenantCommandHandler.cs index 16d0821..4887de8 100644 --- a/CleanArchitecture.Domain/Commands/Tenants/CreateTenant/CreateTenantCommandHandler.cs +++ b/CleanArchitecture.Domain/Commands/Tenants/CreateTenant/CreateTenantCommandHandler.cs @@ -3,10 +3,10 @@ using System.Threading.Tasks; using CleanArchitecture.Domain.Entities; using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.Tenant; using CleanArchitecture.Domain.Interfaces; using CleanArchitecture.Domain.Interfaces.Repositories; using CleanArchitecture.Domain.Notifications; +using CleanArchitecture.Shared.Events.Tenant; using MediatR; namespace CleanArchitecture.Domain.Commands.Tenants.CreateTenant; diff --git a/CleanArchitecture.Domain/Commands/Tenants/DeleteTenant/DeleteTenantCommandHandler.cs b/CleanArchitecture.Domain/Commands/Tenants/DeleteTenant/DeleteTenantCommandHandler.cs index 1303d7b..ea30b35 100644 --- a/CleanArchitecture.Domain/Commands/Tenants/DeleteTenant/DeleteTenantCommandHandler.cs +++ b/CleanArchitecture.Domain/Commands/Tenants/DeleteTenant/DeleteTenantCommandHandler.cs @@ -3,10 +3,10 @@ using System.Threading; using System.Threading.Tasks; using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.Tenant; using CleanArchitecture.Domain.Interfaces; using CleanArchitecture.Domain.Interfaces.Repositories; using CleanArchitecture.Domain.Notifications; +using CleanArchitecture.Shared.Events.Tenant; using MediatR; namespace CleanArchitecture.Domain.Commands.Tenants.DeleteTenant; diff --git a/CleanArchitecture.Domain/Commands/Tenants/UpdateTenant/UpdateTenantCommandHandler.cs b/CleanArchitecture.Domain/Commands/Tenants/UpdateTenant/UpdateTenantCommandHandler.cs index dc1e076..9e445cb 100644 --- a/CleanArchitecture.Domain/Commands/Tenants/UpdateTenant/UpdateTenantCommandHandler.cs +++ b/CleanArchitecture.Domain/Commands/Tenants/UpdateTenant/UpdateTenantCommandHandler.cs @@ -2,10 +2,10 @@ using System.Threading; using System.Threading.Tasks; using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.Tenant; using CleanArchitecture.Domain.Interfaces; using CleanArchitecture.Domain.Interfaces.Repositories; using CleanArchitecture.Domain.Notifications; +using CleanArchitecture.Shared.Events.Tenant; using MediatR; namespace CleanArchitecture.Domain.Commands.Tenants.UpdateTenant; diff --git a/CleanArchitecture.Domain/Commands/Users/ChangePassword/ChangePasswordCommandHandler.cs b/CleanArchitecture.Domain/Commands/Users/ChangePassword/ChangePasswordCommandHandler.cs index 713d43e..19d0616 100644 --- a/CleanArchitecture.Domain/Commands/Users/ChangePassword/ChangePasswordCommandHandler.cs +++ b/CleanArchitecture.Domain/Commands/Users/ChangePassword/ChangePasswordCommandHandler.cs @@ -1,10 +1,10 @@ using System.Threading; using System.Threading.Tasks; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.User; using CleanArchitecture.Domain.Interfaces; using CleanArchitecture.Domain.Interfaces.Repositories; using CleanArchitecture.Domain.Notifications; +using CleanArchitecture.Shared.Events.User; using MediatR; using BC = BCrypt.Net.BCrypt; diff --git a/CleanArchitecture.Domain/Commands/Users/CreateUser/CreateUserCommandHandler.cs b/CleanArchitecture.Domain/Commands/Users/CreateUser/CreateUserCommandHandler.cs index 39d3c2d..1956f5d 100644 --- a/CleanArchitecture.Domain/Commands/Users/CreateUser/CreateUserCommandHandler.cs +++ b/CleanArchitecture.Domain/Commands/Users/CreateUser/CreateUserCommandHandler.cs @@ -3,10 +3,10 @@ using System.Threading.Tasks; using CleanArchitecture.Domain.Entities; using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.User; using CleanArchitecture.Domain.Interfaces; using CleanArchitecture.Domain.Interfaces.Repositories; using CleanArchitecture.Domain.Notifications; +using CleanArchitecture.Shared.Events.User; using MediatR; using BC = BCrypt.Net.BCrypt; diff --git a/CleanArchitecture.Domain/Commands/Users/DeleteUser/DeleteUserCommandHandler.cs b/CleanArchitecture.Domain/Commands/Users/DeleteUser/DeleteUserCommandHandler.cs index ef174bc..d3eba80 100644 --- a/CleanArchitecture.Domain/Commands/Users/DeleteUser/DeleteUserCommandHandler.cs +++ b/CleanArchitecture.Domain/Commands/Users/DeleteUser/DeleteUserCommandHandler.cs @@ -2,10 +2,10 @@ using System.Threading; using System.Threading.Tasks; using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.User; using CleanArchitecture.Domain.Interfaces; using CleanArchitecture.Domain.Interfaces.Repositories; using CleanArchitecture.Domain.Notifications; +using CleanArchitecture.Shared.Events.User; using MediatR; namespace CleanArchitecture.Domain.Commands.Users.DeleteUser; diff --git a/CleanArchitecture.Domain/Commands/Users/LoginUser/LoginUserCommandHandler.cs b/CleanArchitecture.Domain/Commands/Users/LoginUser/LoginUserCommandHandler.cs index 081689c..2a72fae 100644 --- a/CleanArchitecture.Domain/Commands/Users/LoginUser/LoginUserCommandHandler.cs +++ b/CleanArchitecture.Domain/Commands/Users/LoginUser/LoginUserCommandHandler.cs @@ -68,7 +68,7 @@ public sealed class LoginUserCommandHandler : CommandHandlerBase, return ""; } - + user.SetActive(); user.SetLastLoggedinDate(DateTimeOffset.Now); diff --git a/CleanArchitecture.Domain/Commands/Users/UpdateUser/UpdateUserCommandHandler.cs b/CleanArchitecture.Domain/Commands/Users/UpdateUser/UpdateUserCommandHandler.cs index 83ad83f..518ea37 100644 --- a/CleanArchitecture.Domain/Commands/Users/UpdateUser/UpdateUserCommandHandler.cs +++ b/CleanArchitecture.Domain/Commands/Users/UpdateUser/UpdateUserCommandHandler.cs @@ -2,10 +2,10 @@ using System.Threading; using System.Threading.Tasks; using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Errors; -using CleanArchitecture.Domain.Events.User; using CleanArchitecture.Domain.Interfaces; using CleanArchitecture.Domain.Interfaces.Repositories; using CleanArchitecture.Domain.Notifications; +using CleanArchitecture.Shared.Events.User; using MediatR; namespace CleanArchitecture.Domain.Commands.Users.UpdateUser; diff --git a/CleanArchitecture.Domain/Constants/Messaging.cs b/CleanArchitecture.Domain/Constants/Messaging.cs new file mode 100644 index 0000000..8d2f401 --- /dev/null +++ b/CleanArchitecture.Domain/Constants/Messaging.cs @@ -0,0 +1,6 @@ +namespace CleanArchitecture.Domain.Constants; + +public sealed class Messaging +{ + public const string ExchangeNameNotifications = "exchange-notifications"; +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/DomainEvents/IDomainEventStore.cs b/CleanArchitecture.Domain/DomainEvents/IDomainEventStore.cs index 68b17b5..f27f89e 100644 --- a/CleanArchitecture.Domain/DomainEvents/IDomainEventStore.cs +++ b/CleanArchitecture.Domain/DomainEvents/IDomainEventStore.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using CleanArchitecture.Shared.Events; namespace CleanArchitecture.Domain.DomainEvents; diff --git a/CleanArchitecture.Domain/DomainEvents/StoredDomainEvent.cs b/CleanArchitecture.Domain/DomainEvents/StoredDomainEvent.cs index 5220441..f443e98 100644 --- a/CleanArchitecture.Domain/DomainEvents/StoredDomainEvent.cs +++ b/CleanArchitecture.Domain/DomainEvents/StoredDomainEvent.cs @@ -1,4 +1,5 @@ using System; +using CleanArchitecture.Shared.Events; namespace CleanArchitecture.Domain.DomainEvents; diff --git a/CleanArchitecture.Domain/Entities/User.cs b/CleanArchitecture.Domain/Entities/User.cs index 292007c..16e4e8f 100644 --- a/CleanArchitecture.Domain/Entities/User.cs +++ b/CleanArchitecture.Domain/Entities/User.cs @@ -66,7 +66,7 @@ public class User : Entity { TenantId = tenantId; } - + public void SetLastLoggedinDate(DateTimeOffset lastLoggedinDate) { LastLoggedinDate = lastLoggedinDate; @@ -76,7 +76,7 @@ public class User : Entity { Status = UserStatus.Inactive; } - + public void SetActive() { Status = UserStatus.Active; diff --git a/CleanArchitecture.Domain/EventHandler/Fanout/FanoutEventHandler.cs b/CleanArchitecture.Domain/EventHandler/Fanout/FanoutEventHandler.cs new file mode 100644 index 0000000..8d3ae4d --- /dev/null +++ b/CleanArchitecture.Domain/EventHandler/Fanout/FanoutEventHandler.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using CleanArchitecture.Domain.Constants; +using CleanArchitecture.Domain.Rabbitmq; +using CleanArchitecture.Shared.Events; + +namespace CleanArchitecture.Domain.EventHandler.Fanout; + +public sealed class FanoutEventHandler : IFanoutEventHandler +{ + private readonly RabbitMqHandler _rabbitMqHandler; + + public FanoutEventHandler( + RabbitMqHandler rabbitMqHandler) + { + _rabbitMqHandler = rabbitMqHandler; + _rabbitMqHandler.InitializeExchange(Messaging.ExchangeNameNotifications); + } + + public Task HandleDomainEventAsync(DomainEvent @event) + { + _rabbitMqHandler.EnqueueExchangeMessage( + Messaging.ExchangeNameNotifications, + @event); + + return Task.FromResult(@event); + } +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/EventHandler/Fanout/IFanoutEventHandler.cs b/CleanArchitecture.Domain/EventHandler/Fanout/IFanoutEventHandler.cs new file mode 100644 index 0000000..26453ac --- /dev/null +++ b/CleanArchitecture.Domain/EventHandler/Fanout/IFanoutEventHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using CleanArchitecture.Shared.Events; + +namespace CleanArchitecture.Domain.EventHandler.Fanout; + +public interface IFanoutEventHandler +{ + Task HandleDomainEventAsync(DomainEvent @event); +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/EventHandler/TenantEventHandler.cs b/CleanArchitecture.Domain/EventHandler/TenantEventHandler.cs index 7803f55..9f14c43 100644 --- a/CleanArchitecture.Domain/EventHandler/TenantEventHandler.cs +++ b/CleanArchitecture.Domain/EventHandler/TenantEventHandler.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; using CleanArchitecture.Domain.Entities; -using CleanArchitecture.Domain.Events.Tenant; +using CleanArchitecture.Shared.Events.Tenant; using MediatR; using Microsoft.Extensions.Caching.Distributed; diff --git a/CleanArchitecture.Domain/EventHandler/UserEventHandler.cs b/CleanArchitecture.Domain/EventHandler/UserEventHandler.cs index 4a2263a..95d61a8 100644 --- a/CleanArchitecture.Domain/EventHandler/UserEventHandler.cs +++ b/CleanArchitecture.Domain/EventHandler/UserEventHandler.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; using CleanArchitecture.Domain.Entities; -using CleanArchitecture.Domain.Events.User; +using CleanArchitecture.Shared.Events.User; using MediatR; using Microsoft.Extensions.Caching.Distributed; diff --git a/CleanArchitecture.Domain/Extensions/ServiceCollectionExtension.cs b/CleanArchitecture.Domain/Extensions/ServiceCollectionExtension.cs index eddd6e7..615be19 100644 --- a/CleanArchitecture.Domain/Extensions/ServiceCollectionExtension.cs +++ b/CleanArchitecture.Domain/Extensions/ServiceCollectionExtension.cs @@ -7,9 +7,10 @@ using CleanArchitecture.Domain.Commands.Users.DeleteUser; using CleanArchitecture.Domain.Commands.Users.LoginUser; using CleanArchitecture.Domain.Commands.Users.UpdateUser; using CleanArchitecture.Domain.EventHandler; -using CleanArchitecture.Domain.Events.Tenant; -using CleanArchitecture.Domain.Events.User; +using CleanArchitecture.Domain.EventHandler.Fanout; using CleanArchitecture.Domain.Interfaces; +using CleanArchitecture.Shared.Events.Tenant; +using CleanArchitecture.Shared.Events.User; using MediatR; using Microsoft.Extensions.DependencyInjection; @@ -36,6 +37,9 @@ public static class ServiceCollectionExtension public static IServiceCollection AddNotificationHandlers(this IServiceCollection services) { + // Fanout + services.AddScoped(); + // User services.AddScoped, UserEventHandler>(); services.AddScoped, UserEventHandler>(); diff --git a/CleanArchitecture.Domain/Interfaces/IMediatorHandler.cs b/CleanArchitecture.Domain/Interfaces/IMediatorHandler.cs index e8c5ef4..fbfa614 100644 --- a/CleanArchitecture.Domain/Interfaces/IMediatorHandler.cs +++ b/CleanArchitecture.Domain/Interfaces/IMediatorHandler.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using CleanArchitecture.Domain.Commands; -using CleanArchitecture.Domain.DomainEvents; +using CleanArchitecture.Shared.Events; using MediatR; namespace CleanArchitecture.Domain.Interfaces; diff --git a/CleanArchitecture.Domain/Notifications/DomainNotification.cs b/CleanArchitecture.Domain/Notifications/DomainNotification.cs index c228ab4..ac81d3a 100644 --- a/CleanArchitecture.Domain/Notifications/DomainNotification.cs +++ b/CleanArchitecture.Domain/Notifications/DomainNotification.cs @@ -1,5 +1,5 @@ using System; -using CleanArchitecture.Domain.DomainEvents; +using CleanArchitecture.Shared.Events; namespace CleanArchitecture.Domain.Notifications; diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/BindQueueToExchange.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/BindQueueToExchange.cs new file mode 100644 index 0000000..72d3f81 --- /dev/null +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/BindQueueToExchange.cs @@ -0,0 +1,22 @@ +using RabbitMQ.Client; + +namespace CleanArchitecture.Domain.Rabbitmq.Actions; + +public sealed class BindQueueToExchange : IRabbitMqAction +{ + private readonly string _exchangeName; + private readonly string _queueName; + private readonly string _routingKey; + + public BindQueueToExchange(string queueName, string exchangeName, string routingKey = "") + { + _exchangeName = exchangeName; + _routingKey = routingKey; + _queueName = queueName; + } + + public void Perform(IModel channel) + { + channel.QueueBind(_queueName, _exchangeName, _routingKey); + } +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/CreateExchange.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/CreateExchange.cs new file mode 100644 index 0000000..ee28a42 --- /dev/null +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/CreateExchange.cs @@ -0,0 +1,20 @@ +using RabbitMQ.Client; + +namespace CleanArchitecture.Domain.Rabbitmq.Actions; + +public sealed class CreateExchange : IRabbitMqAction +{ + private readonly string _name; + private readonly string _type; + + public CreateExchange(string name, string type) + { + _name = name; + _type = type; + } + + public void Perform(IModel channel) + { + channel.ExchangeDeclare(_name, _type); + } +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/CreateQueue.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/CreateQueue.cs new file mode 100644 index 0000000..dba3883 --- /dev/null +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/CreateQueue.cs @@ -0,0 +1,23 @@ +using RabbitMQ.Client; + +namespace CleanArchitecture.Domain.Rabbitmq.Actions; + +public sealed class CreateQueue : IRabbitMqAction +{ + public string QueueName { get; } + + public CreateQueue(string queueName) + { + QueueName = queueName; + } + + public void Perform(IModel channel) + { + channel.QueueDeclare( + QueueName, + false, + false, + false, + null); + } +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/IRabbitMqAction.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/IRabbitMqAction.cs new file mode 100644 index 0000000..4570bf4 --- /dev/null +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/IRabbitMqAction.cs @@ -0,0 +1,8 @@ +using RabbitMQ.Client; + +namespace CleanArchitecture.Domain.Rabbitmq.Actions; + +public interface IRabbitMqAction +{ + void Perform(IModel channel); +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/RegisterConsumer.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/RegisterConsumer.cs new file mode 100644 index 0000000..52fbf6c --- /dev/null +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/RegisterConsumer.cs @@ -0,0 +1,32 @@ +using System; +using RabbitMQ.Client; + +namespace CleanArchitecture.Domain.Rabbitmq.Actions; + +public sealed class RegisterConsumer : IRabbitMqAction +{ + private readonly Action _addConsumer; + private readonly ConsumeEventHandler _consumer; + private readonly string _exchange; + private readonly string _queue; + private readonly string _routingKey; + + public RegisterConsumer( + string exchange, + string queue, + string routingKey, + ConsumeEventHandler consumer, + Action addConsumer) + { + _exchange = exchange; + _queue = queue; + _routingKey = routingKey; + _consumer = consumer; + _addConsumer = addConsumer; + } + + public void Perform(IModel channel) + { + _addConsumer(_exchange, _queue, _routingKey, _consumer); + } +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/SendAcknowledgement.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/SendAcknowledgement.cs new file mode 100644 index 0000000..01e3e11 --- /dev/null +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/SendAcknowledgement.cs @@ -0,0 +1,18 @@ +using RabbitMQ.Client; + +namespace CleanArchitecture.Domain.Rabbitmq.Actions; + +public sealed class SendAcknowledgement : IRabbitMqAction +{ + public ulong DeliveryTag { get; } + + public SendAcknowledgement(ulong deliveryTag) + { + DeliveryTag = deliveryTag; + } + + public void Perform(IModel channel) + { + channel.BasicAck(DeliveryTag, false); + } +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/SendMessage.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/SendMessage.cs new file mode 100644 index 0000000..213bff4 --- /dev/null +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/SendMessage.cs @@ -0,0 +1,38 @@ +using System.Text; +using Newtonsoft.Json; +using RabbitMQ.Client; + +namespace CleanArchitecture.Domain.Rabbitmq.Actions; + +public sealed class SendMessage : IRabbitMqAction +{ + private static readonly JsonSerializerSettings s_serializerSettings = + new() { TypeNameHandling = TypeNameHandling.Objects }; + + private readonly string _exchange; + private readonly object _message; + + private readonly string _routingKey; + + + /// If exchange is empty, this is the name of the queue + public SendMessage(string routingKey, string exchange, object message) + { + _routingKey = routingKey; + _exchange = exchange; + _message = message; + } + + public void Perform(IModel channel) + { + var json = JsonConvert.SerializeObject(_message, s_serializerSettings); + + var content = Encoding.UTF8.GetBytes(json); + + channel.BasicPublish( + _exchange, + _routingKey, + null, + content); + } +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Delegates.cs b/CleanArchitecture.Domain/Rabbitmq/Delegates.cs new file mode 100644 index 0000000..70e87c7 --- /dev/null +++ b/CleanArchitecture.Domain/Rabbitmq/Delegates.cs @@ -0,0 +1,6 @@ +using System; +using System.Threading.Tasks; + +namespace CleanArchitecture.Domain.Rabbitmq; + +public delegate Task ConsumeEventHandler(ReadOnlyMemory content); \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Extensions/ServiceCollectionExtensions.cs b/CleanArchitecture.Domain/Rabbitmq/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..42ba1ca --- /dev/null +++ b/CleanArchitecture.Domain/Rabbitmq/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace CleanArchitecture.Domain.Rabbitmq.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddRabbitMqHandler( + this IServiceCollection services, + IConfiguration configuration, + string rabbitMqConfigSection) + { + var rabbitMq = new RabbitMqConfiguration(); + configuration.Bind(rabbitMqConfigSection, rabbitMq); + services.AddSingleton(rabbitMq); + + services.AddSingleton(); + services.AddHostedService(serviceProvider => serviceProvider.GetService()!); + + return services; + } +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/RabbitMqConfiguration.cs b/CleanArchitecture.Domain/Rabbitmq/RabbitMqConfiguration.cs new file mode 100644 index 0000000..fc9ec38 --- /dev/null +++ b/CleanArchitecture.Domain/Rabbitmq/RabbitMqConfiguration.cs @@ -0,0 +1,9 @@ +namespace CleanArchitecture.Domain.Rabbitmq; + +public sealed class RabbitMqConfiguration +{ + public string Host { get; set; } = string.Empty; + public bool Enabled { get; set; } + public string Username { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs b/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs new file mode 100644 index 0000000..09c20b0 --- /dev/null +++ b/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using CleanArchitecture.Domain.Rabbitmq.Actions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; + +namespace CleanArchitecture.Domain.Rabbitmq; + +public sealed class RabbitMqHandler : BackgroundService +{ + private readonly IModel? _channel; + private readonly RabbitMqConfiguration _configuration; + + private readonly ConcurrentDictionary> _consumers = new(); + + private readonly ILogger _logger; + + private readonly ConcurrentQueue _pendingActions = new(); + + public RabbitMqHandler( + RabbitMqConfiguration configuration, + ILogger logger) + { + _configuration = configuration; + _logger = logger; + + if (!configuration.Enabled) + { + logger.LogInformation("RabbitMQ is disabled. Connection will not be established"); + return; + } + + var factory = new ConnectionFactory + { + AutomaticRecoveryEnabled = true, + HostName = configuration.Host, + UserName = configuration.Username, + Password = configuration.Password, + DispatchConsumersAsync = true + }; + + var connection = factory.CreateConnection(); + _channel = connection.CreateModel(); + } + + public void InitializeExchange(string exchangeName, string type = ExchangeType.Fanout) + { + if (!_configuration.Enabled) + { + _logger.LogInformation($"RabbitMQ is disabled. Skipping the creation of exchange {exchangeName}."); + return; + } + + _pendingActions.Enqueue(new CreateExchange(exchangeName, type)); + } + + public void InitializeQueues(params string[] queueNames) + { + if (!_configuration.Enabled) + { + _logger.LogInformation("RabbitMQ is disabled. Skipping the creation of queues."); + return; + } + + foreach (var queue in queueNames) + { + _pendingActions.Enqueue(new CreateQueue(queue)); + } + } + + public void BindQueueToExchange(string queueName, string exchangeName, string routingKey = "") + { + if (!_configuration.Enabled) + { + _logger.LogInformation("RabbitMQ is disabled. Skipping the binding of queue to exchange."); + return; + } + + _pendingActions.Enqueue(new BindQueueToExchange(queueName, exchangeName, routingKey)); + } + + public void AddConsumer(string queueName, ConsumeEventHandler consumer) + { + if (!_configuration.Enabled) + { + _logger.LogInformation("RabbitMQ is disabled. Skipping the addition of consumer."); + return; + } + + // routingKey is set to queueName to mimic rabbitMQ + _pendingActions.Enqueue( + new RegisterConsumer( + string.Empty, + queueName, + queueName, + consumer, + AddEventConsumer)); + } + + public void AddExchangeConsumer(string exchange, string routingKey, string queue, ConsumeEventHandler consumer) + { + if (!_configuration.Enabled) + { + _logger.LogInformation("RabbitMQ is disabled. Skipping the addition of exchange consumer."); + return; + } + + _pendingActions.Enqueue( + new RegisterConsumer( + exchange, + queue, + routingKey, + consumer, + AddEventConsumer)); + } + + public void AddExchangeConsumer(string exchange, string queue, ConsumeEventHandler consumer) + { + AddExchangeConsumer(exchange, string.Empty, queue, consumer); + } + + private void AddEventConsumer(string exchange, string queueName, string routingKey, ConsumeEventHandler consumer) + { + var key = $"{exchange}-{routingKey}"; + + if (!_consumers.TryGetValue(key, out var consumers)) + { + consumers = new List(); + _consumers.TryAdd(key, consumers); + + var eventHandler = new AsyncEventingBasicConsumer(_channel); + eventHandler.Received += CallEventConsumersAsync; + + _channel!.BasicConsume(queueName, false, eventHandler); + } + + consumers.Add(consumer); + } + + private async Task CallEventConsumersAsync(object sender, BasicDeliverEventArgs ea) + { + var key = $"{ea.Exchange}-{ea.RoutingKey}"; + + if (!_consumers.TryGetValue(key, out var consumers)) + { + return; + } + + foreach (var consumer in consumers) + { + try + { + await consumer(ea.Body); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error while handling event in queue {ea.RoutingKey}"); + } + } + + _pendingActions.Enqueue(new SendAcknowledgement(ea.DeliveryTag)); + } + + + public void EnqueueMessage(string queueName, object message) + { + if (!_configuration.Enabled) + { + _logger.LogInformation("RabbitMQ is disabled. Skipping enqueueing of message"); + return; + } + + _pendingActions.Enqueue(new SendMessage(queueName, string.Empty, message)); + } + + public void EnqueueExchangeMessage(string exchange, object message, string routingKey = "") + { + if (!_configuration.Enabled) + { + _logger.LogInformation("RabbitMQ is disabled. Skipping enqueueing of message"); + return; + } + + _pendingActions.Enqueue(new SendMessage(routingKey, exchange, message)); + } + + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + if (!_configuration.Enabled) + { + _logger.LogInformation("RabbitMQ is disabled. Message handling loop will not be started"); + return; + } + + while (true) + { + HandleEnqueuedActions(); + + await Task.Delay(1000, stoppingToken); + } + } + + private void HandleEnqueuedActions() + { + while (_pendingActions.TryDequeue(out var action)) + { + try + { + action.Perform(_channel!); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while trying to send a rabbitmq message"); + _pendingActions.Enqueue(action); + } + } + } +} \ No newline at end of file diff --git a/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj b/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj index a31690e..1a8531e 100644 --- a/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj +++ b/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj @@ -8,10 +8,10 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -23,7 +23,7 @@ - + diff --git a/CleanArchitecture.Infrastructure.Tests/InMemoryBusTests.cs b/CleanArchitecture.Infrastructure.Tests/InMemoryBusTests.cs index bd976e1..b65bcbb 100644 --- a/CleanArchitecture.Infrastructure.Tests/InMemoryBusTests.cs +++ b/CleanArchitecture.Infrastructure.Tests/InMemoryBusTests.cs @@ -2,8 +2,9 @@ using System; using System.Threading.Tasks; using CleanArchitecture.Domain.Commands.Users.DeleteUser; using CleanArchitecture.Domain.DomainEvents; -using CleanArchitecture.Domain.Events.User; +using CleanArchitecture.Domain.EventHandler.Fanout; using CleanArchitecture.Domain.Notifications; +using CleanArchitecture.Shared.Events.User; using MediatR; using NSubstitute; using Xunit; @@ -17,8 +18,9 @@ public sealed class InMemoryBusTests { var mediator = Substitute.For(); var domainEventStore = Substitute.For(); + var fanoutEventHandler = Substitute.For(); - var inMemoryBus = new InMemoryBus(mediator, domainEventStore); + var inMemoryBus = new InMemoryBus(mediator, domainEventStore, fanoutEventHandler); const string key = "Key"; const string value = "Value"; @@ -36,8 +38,9 @@ public sealed class InMemoryBusTests { var mediator = Substitute.For(); var domainEventStore = Substitute.For(); + var fanoutEventHandler = Substitute.For(); - var inMemoryBus = new InMemoryBus(mediator, domainEventStore); + var inMemoryBus = new InMemoryBus(mediator, domainEventStore, fanoutEventHandler); var userDeletedEvent = new UserDeletedEvent(Guid.NewGuid(), Guid.NewGuid()); @@ -51,8 +54,9 @@ public sealed class InMemoryBusTests { var mediator = Substitute.For(); var domainEventStore = Substitute.For(); + var fanoutEventHandler = Substitute.For(); - var inMemoryBus = new InMemoryBus(mediator, domainEventStore); + var inMemoryBus = new InMemoryBus(mediator, domainEventStore, fanoutEventHandler); var deleteUserCommand = new DeleteUserCommand(Guid.NewGuid()); diff --git a/CleanArchitecture.Infrastructure/CleanArchitecture.Infrastructure.csproj b/CleanArchitecture.Infrastructure/CleanArchitecture.Infrastructure.csproj index 4aab3e5..0c47055 100644 --- a/CleanArchitecture.Infrastructure/CleanArchitecture.Infrastructure.csproj +++ b/CleanArchitecture.Infrastructure/CleanArchitecture.Infrastructure.csproj @@ -7,6 +7,7 @@ + diff --git a/CleanArchitecture.Infrastructure/EventSourcing/EventStore.cs b/CleanArchitecture.Infrastructure/EventSourcing/EventStore.cs index 361156d..9dc8c20 100644 --- a/CleanArchitecture.Infrastructure/EventSourcing/EventStore.cs +++ b/CleanArchitecture.Infrastructure/EventSourcing/EventStore.cs @@ -3,6 +3,7 @@ using CleanArchitecture.Domain.DomainEvents; using CleanArchitecture.Domain.DomainNotifications; using CleanArchitecture.Domain.Notifications; using CleanArchitecture.Infrastructure.Database; +using CleanArchitecture.Shared.Events; using Newtonsoft.Json; namespace CleanArchitecture.Infrastructure.EventSourcing; diff --git a/CleanArchitecture.Infrastructure/InMemoryBus.cs b/CleanArchitecture.Infrastructure/InMemoryBus.cs index 703cbb2..b41cc62 100644 --- a/CleanArchitecture.Infrastructure/InMemoryBus.cs +++ b/CleanArchitecture.Infrastructure/InMemoryBus.cs @@ -1,7 +1,9 @@ using System.Threading.Tasks; using CleanArchitecture.Domain.Commands; using CleanArchitecture.Domain.DomainEvents; +using CleanArchitecture.Domain.EventHandler.Fanout; using CleanArchitecture.Domain.Interfaces; +using CleanArchitecture.Shared.Events; using MediatR; namespace CleanArchitecture.Infrastructure; @@ -9,14 +11,17 @@ namespace CleanArchitecture.Infrastructure; public sealed class InMemoryBus : IMediatorHandler { private readonly IDomainEventStore _domainEventStore; + private readonly IFanoutEventHandler _fanoutEventHandler; private readonly IMediator _mediator; public InMemoryBus( IMediator mediator, - IDomainEventStore domainEventStore) + IDomainEventStore domainEventStore, + IFanoutEventHandler fanoutEventHandler) { _mediator = mediator; _domainEventStore = domainEventStore; + _fanoutEventHandler = fanoutEventHandler; } public Task QueryAsync(IRequest query) @@ -29,6 +34,8 @@ public sealed class InMemoryBus : IMediatorHandler await _domainEventStore.SaveAsync(@event); await _mediator.Publish(@event); + + await _fanoutEventHandler.HandleDomainEventAsync(@event); } public Task SendCommandAsync(T command) where T : CommandBase diff --git a/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj b/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj index ad4c6ae..2890dea 100644 --- a/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj +++ b/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj @@ -8,14 +8,14 @@ - - - - - - - - + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -27,8 +27,8 @@ - - + + diff --git a/CleanArchitecture.Proto/CleanArchitecture.Proto.csproj b/CleanArchitecture.Proto/CleanArchitecture.Proto.csproj index 9f45389..7df8022 100644 --- a/CleanArchitecture.Proto/CleanArchitecture.Proto.csproj +++ b/CleanArchitecture.Proto/CleanArchitecture.Proto.csproj @@ -6,16 +6,16 @@ - - - - + + + + - - - + + + diff --git a/CleanArchitecture.Shared/CleanArchitecture.Shared.csproj b/CleanArchitecture.Shared/CleanArchitecture.Shared.csproj index 7547394..812e7fd 100644 --- a/CleanArchitecture.Shared/CleanArchitecture.Shared.csproj +++ b/CleanArchitecture.Shared/CleanArchitecture.Shared.csproj @@ -6,4 +6,8 @@ + + + + diff --git a/CleanArchitecture.Domain/DomainEvents/DomainEvent.cs b/CleanArchitecture.Shared/Events/DomainEvent.cs similarity index 89% rename from CleanArchitecture.Domain/DomainEvents/DomainEvent.cs rename to CleanArchitecture.Shared/Events/DomainEvent.cs index 37d8eaf..f541549 100644 --- a/CleanArchitecture.Domain/DomainEvents/DomainEvent.cs +++ b/CleanArchitecture.Shared/Events/DomainEvent.cs @@ -1,7 +1,7 @@ using System; using MediatR; -namespace CleanArchitecture.Domain.DomainEvents; +namespace CleanArchitecture.Shared.Events; public abstract class DomainEvent : Message, INotification { diff --git a/CleanArchitecture.Domain/DomainEvents/Message.cs b/CleanArchitecture.Shared/Events/Message.cs similarity index 90% rename from CleanArchitecture.Domain/DomainEvents/Message.cs rename to CleanArchitecture.Shared/Events/Message.cs index a99929e..70a7198 100644 --- a/CleanArchitecture.Domain/DomainEvents/Message.cs +++ b/CleanArchitecture.Shared/Events/Message.cs @@ -1,7 +1,7 @@ using System; using MediatR; -namespace CleanArchitecture.Domain.DomainEvents; +namespace CleanArchitecture.Shared.Events; public abstract class Message : IRequest { diff --git a/CleanArchitecture.Domain/Events/Tenant/TenantCreatedEvent.cs b/CleanArchitecture.Shared/Events/Tenant/TenantCreatedEvent.cs similarity index 69% rename from CleanArchitecture.Domain/Events/Tenant/TenantCreatedEvent.cs rename to CleanArchitecture.Shared/Events/Tenant/TenantCreatedEvent.cs index dc558b6..07e8afd 100644 --- a/CleanArchitecture.Domain/Events/Tenant/TenantCreatedEvent.cs +++ b/CleanArchitecture.Shared/Events/Tenant/TenantCreatedEvent.cs @@ -1,7 +1,6 @@ using System; -using CleanArchitecture.Domain.DomainEvents; -namespace CleanArchitecture.Domain.Events.Tenant; +namespace CleanArchitecture.Shared.Events.Tenant; public sealed class TenantCreatedEvent : DomainEvent { diff --git a/CleanArchitecture.Domain/Events/Tenant/TenantDeletedEvent.cs b/CleanArchitecture.Shared/Events/Tenant/TenantDeletedEvent.cs similarity index 60% rename from CleanArchitecture.Domain/Events/Tenant/TenantDeletedEvent.cs rename to CleanArchitecture.Shared/Events/Tenant/TenantDeletedEvent.cs index e96bdfd..6aabe90 100644 --- a/CleanArchitecture.Domain/Events/Tenant/TenantDeletedEvent.cs +++ b/CleanArchitecture.Shared/Events/Tenant/TenantDeletedEvent.cs @@ -1,7 +1,6 @@ using System; -using CleanArchitecture.Domain.DomainEvents; -namespace CleanArchitecture.Domain.Events.Tenant; +namespace CleanArchitecture.Shared.Events.Tenant; public sealed class TenantDeletedEvent : DomainEvent { diff --git a/CleanArchitecture.Domain/Events/Tenant/TenantUpdatedEvent.cs b/CleanArchitecture.Shared/Events/Tenant/TenantUpdatedEvent.cs similarity index 69% rename from CleanArchitecture.Domain/Events/Tenant/TenantUpdatedEvent.cs rename to CleanArchitecture.Shared/Events/Tenant/TenantUpdatedEvent.cs index 20675d1..d8a678c 100644 --- a/CleanArchitecture.Domain/Events/Tenant/TenantUpdatedEvent.cs +++ b/CleanArchitecture.Shared/Events/Tenant/TenantUpdatedEvent.cs @@ -1,7 +1,6 @@ using System; -using CleanArchitecture.Domain.DomainEvents; -namespace CleanArchitecture.Domain.Events.Tenant; +namespace CleanArchitecture.Shared.Events.Tenant; public sealed class TenantUpdatedEvent : DomainEvent { diff --git a/CleanArchitecture.Domain/Events/User/PasswordChangedEvent.cs b/CleanArchitecture.Shared/Events/User/PasswordChangedEvent.cs similarity index 61% rename from CleanArchitecture.Domain/Events/User/PasswordChangedEvent.cs rename to CleanArchitecture.Shared/Events/User/PasswordChangedEvent.cs index c5b1b25..427c407 100644 --- a/CleanArchitecture.Domain/Events/User/PasswordChangedEvent.cs +++ b/CleanArchitecture.Shared/Events/User/PasswordChangedEvent.cs @@ -1,7 +1,6 @@ using System; -using CleanArchitecture.Domain.DomainEvents; -namespace CleanArchitecture.Domain.Events.User; +namespace CleanArchitecture.Shared.Events.User; public sealed class PasswordChangedEvent : DomainEvent { diff --git a/CleanArchitecture.Domain/Events/User/UserCreatedEvent.cs b/CleanArchitecture.Shared/Events/User/UserCreatedEvent.cs similarity index 70% rename from CleanArchitecture.Domain/Events/User/UserCreatedEvent.cs rename to CleanArchitecture.Shared/Events/User/UserCreatedEvent.cs index 014c7ea..2cbae8d 100644 --- a/CleanArchitecture.Domain/Events/User/UserCreatedEvent.cs +++ b/CleanArchitecture.Shared/Events/User/UserCreatedEvent.cs @@ -1,7 +1,6 @@ using System; -using CleanArchitecture.Domain.DomainEvents; -namespace CleanArchitecture.Domain.Events.User; +namespace CleanArchitecture.Shared.Events.User; public sealed class UserCreatedEvent : DomainEvent { diff --git a/CleanArchitecture.Domain/Events/User/UserDeletedEvent.cs b/CleanArchitecture.Shared/Events/User/UserDeletedEvent.cs similarity index 70% rename from CleanArchitecture.Domain/Events/User/UserDeletedEvent.cs rename to CleanArchitecture.Shared/Events/User/UserDeletedEvent.cs index 836ae5a..6181c2d 100644 --- a/CleanArchitecture.Domain/Events/User/UserDeletedEvent.cs +++ b/CleanArchitecture.Shared/Events/User/UserDeletedEvent.cs @@ -1,7 +1,6 @@ using System; -using CleanArchitecture.Domain.DomainEvents; -namespace CleanArchitecture.Domain.Events.User; +namespace CleanArchitecture.Shared.Events.User; public sealed class UserDeletedEvent : DomainEvent { diff --git a/CleanArchitecture.Domain/Events/User/UserUpdatedEvent.cs b/CleanArchitecture.Shared/Events/User/UserUpdatedEvent.cs similarity index 70% rename from CleanArchitecture.Domain/Events/User/UserUpdatedEvent.cs rename to CleanArchitecture.Shared/Events/User/UserUpdatedEvent.cs index e72c005..3bfba45 100644 --- a/CleanArchitecture.Domain/Events/User/UserUpdatedEvent.cs +++ b/CleanArchitecture.Shared/Events/User/UserUpdatedEvent.cs @@ -1,7 +1,6 @@ using System; -using CleanArchitecture.Domain.DomainEvents; -namespace CleanArchitecture.Domain.Events.User; +namespace CleanArchitecture.Shared.Events.User; public sealed class UserUpdatedEvent : DomainEvent { diff --git a/CleanArchitecture.gRPC.Tests/CleanArchitecture.gRPC.Tests.csproj b/CleanArchitecture.gRPC.Tests/CleanArchitecture.gRPC.Tests.csproj index 34930d6..7371779 100644 --- a/CleanArchitecture.gRPC.Tests/CleanArchitecture.gRPC.Tests.csproj +++ b/CleanArchitecture.gRPC.Tests/CleanArchitecture.gRPC.Tests.csproj @@ -8,11 +8,11 @@ - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -24,9 +24,9 @@ - - - + + + diff --git a/Readme.md b/Readme.md index ada4b29..7cb783d 100644 --- a/Readme.md +++ b/Readme.md @@ -49,6 +49,7 @@ 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 .` @@ -64,7 +65,8 @@ Running the container 1. Change the ConnectionString in the appsettings.json to `Server=clean-architecture-db-service;Database=clean-architecture;Trusted_Connection=False;MultipleActiveResultSets=true;TrustServerCertificate=True;User Id=SA;Password=Password123!#` 2. Change the RedisHostName in the appsettings.json to `redis-service` -3. Build the docker image and push it to the docker hub (Change the image name in the `k8s-deployment.yml` to your own) +3. Change the RabbitMQ Host in the appsettings.json to `rabbitmq-service` +4. Build the docker image and push it to the docker hub (Change the image name in the `k8s-deployment.yml` to your own) Apply the deployment file: `kubectl apply -f k8s-deployment.yml` (Delete: `kubectl delete -f k8s-deployment.yml`) diff --git a/docker-compose.yml b/docker-compose.yml index d7b5f06..9bafb9c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,10 @@ services: ports: - 80:80 depends_on: - - db + db: + condition: service_started + rabbitmq: + condition: service_healthy links: - db healthcheck: @@ -15,6 +18,7 @@ services: interval: 30s timeout: 5s retries: 3 + db: image: mcr.microsoft.com/mssql/server environment: @@ -22,6 +26,7 @@ services: - SA_PASSWORD=Password123!# ports: - 1433:1433 + redis: image: docker.io/bitnami/redis:7.2 environment: @@ -32,6 +37,24 @@ services: - '6379:6379' volumes: - 'redis_data:/bitnami/redis/data' + + rabbitmq: + image: "rabbitmq:3-management" + ports: + - 5672:5672 + - 15672:15672 + environment: + - RABBITMQ_DEFAULT_USER=admin + - RABBITMQ_DEFAULT_PASS=DOIA9234JF + volumes: + - rabbitmq_data:/var/lib/rabbitmq + healthcheck: + test: rabbitmq-diagnostics -q ping + interval: 10s + timeout: 3s + retries: 3 + volumes: + rabbitmq_data: redis_data: driver: local diff --git a/k8s-deployment.yml b/k8s-deployment.yml index b36dd0a..a0095db 100644 --- a/k8s-deployment.yml +++ b/k8s-deployment.yml @@ -14,7 +14,7 @@ spec: spec: containers: - name: clean-architecture-app - # Replace this with the path to your built image + # Replace this with the path to your built image image: alexdev28/clean-architecture ports: - containerPort: 80 @@ -118,3 +118,54 @@ spec: - protocol: TCP port: 6379 targetPort: 6379 + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rabbitmq-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + containers: + - name: rabbitmq + image: rabbitmq:3-management + ports: + - containerPort: 5672 + - containerPort: 15672 + env: + - name: RABBITMQ_DEFAULT_USER + value: admin + - name: RABBITMQ_DEFAULT_PASS + value: DOIA9234JF + volumeMounts: + - name: rabbitmq-data + mountPath: /var/lib/rabbitmq + volumes: + - name: rabbitmq-data + emptyDir: {} + +--- +apiVersion: v1 +kind: Service +metadata: + name: rabbitmq-service +spec: + selector: + app: rabbitmq + ports: + - name: rabbitmq-port + protocol: TCP + port: 5672 + targetPort: 5672 + - name: rabbitmq-management-port + protocol: TCP + port: 15672 + targetPort: 15672