diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 6d2a7d6..4b09db6 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -27,7 +27,7 @@ jobs: - name: Build run: dotnet build --configuration Release --no-restore - name: Install dotnet-ef tool - run: dotnet tool install --global dotnet-ef --version 3.1.0 + run: dotnet tool install --global dotnet-ef - name: Apply Migrations run: dotnet ef database update -s Microservice.Api/Microservice.Api.csproj -p Microservice.Db/Microservice.Db.csproj -c MicroserviceDbContext - name: Test diff --git a/Dockerfile b/Dockerfile index 0458613..7e7ac18 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ COPY ./Microservice.Api/*.csproj ./Microservice.Api/ COPY ./Microservice.Db/*.csproj ./Microservice.Db/ COPY ./Microservice.Logic/*.csproj ./Microservice.Logic/ COPY ./Microservice.HangfireBackgroundJobServer/*.csproj ./Microservice.HangfireBackgroundJobServer/ -COPY ./Microservice.RabbitMessageBroker/*.csproj ./Microservice.RabbitMessageBroker/ +COPY ./Microservice.RabbitMessageBrokerHelpers/*.csproj ./Microservice.RabbitMessageBrokerHelpers/ COPY ./Microservice.RabbitMessageBroker.Integration.Tests/*.csproj ./Microservice.RabbitMessageBroker.Integration.Tests/ COPY ./Microservice.Api.Integration.Tests/*.csproj ./Microservice.Api.Integration.Tests/ RUN dotnet restore diff --git a/Microservice.Api.Integration.Tests/Infrastructure/APIWebApplicationFactoryExtensions.cs b/Microservice.Api.Integration.Tests/Infrastructure/APIWebApplicationFactoryExtensions.cs index d366fb8..8dae275 100644 --- a/Microservice.Api.Integration.Tests/Infrastructure/APIWebApplicationFactoryExtensions.cs +++ b/Microservice.Api.Integration.Tests/Infrastructure/APIWebApplicationFactoryExtensions.cs @@ -1,12 +1,30 @@ -using System; -using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting.Internal; +using System; +using System.Collections.Generic; +using System.IO; namespace Microservice.Api.Integration.Tests.Infrastructure { public static class APIWebApplicationFactoryExtensions { + public static WebApplicationFactory WithAppSettings( + this WebApplicationFactory factory, + IEnumerable> customSettings = null) + where TStartup : class + => factory.WithWebHostBuilder(x => + { + var projDir = Directory.GetCurrentDirectory(); + var configpath = Path.Combine(projDir, "appsettings.json"); + x.ConfigureServices(c => c.AddSingleton(new HostingEnvironment {EnvironmentName = "Testing"})); + x.ConfigureAppConfiguration((c, d) => d.AddJsonFile(configpath)); + if (customSettings != null) + x.ConfigureAppConfiguration((c, d) => d.AddInMemoryCollection(customSettings)); + }); + public static WebApplicationFactory Seed( this WebApplicationFactory factory, Action seed) @@ -22,5 +40,13 @@ public static WebApplicationFactory Seed( } return factory; } + + public static TService GetScopedService( + this WebApplicationFactory factory) + where TStartup : class + => factory.Server.Services.CreateScope().ServiceProvider.GetService(); + + + } } diff --git a/Microservice.Api.Integration.Tests/Infrastructure/BaseControllerTest.cs b/Microservice.Api.Integration.Tests/Infrastructure/BaseControllerTest.cs new file mode 100644 index 0000000..c039cc1 --- /dev/null +++ b/Microservice.Api.Integration.Tests/Infrastructure/BaseControllerTest.cs @@ -0,0 +1,41 @@ +using Microservice.Db; +using Microservice.RabbitMQMessageBrokerExtension; +using Microservice.RabbitMQMessageBrokerExtension.Configuration; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using NUnit.Framework; +using System.Net.Http; + +namespace Microservice.Api.Integration.Tests.Infrastructure +{ + public class BaseControllerTest + { + protected internal WebApplicationFactory Factory; + protected internal HttpClient Client; + protected internal IRabbitMessageBrokerClient MessageBrokerClient; + public string BaseRoute { get; set; } + + [OneTimeSetUp] + public void Setup() + { + Factory = new WebApplicationFactory() + .WithWebHostBuilder(builder => + { + builder.ConfigureServices(services => + { + services.RemoveAll(typeof(DbContextOptions)); + services.RemoveAll(typeof(MicroserviceDbContext)); + services.AddDbContext(); + services.AddRabbitMqMessageBroker(ConfigurationBuilderExtensions.GetCustomSection("RabbitMessageBrokerSettings")); + }); + }); + + Client = Factory.CreateClient(); + MessageBrokerClient = Factory.Services.GetService(); + } + + public string GetBaseRoute() => $"api/{BaseRoute}"; + } +} diff --git a/Microservice.Api.Integration.Tests/Infrastructure/BaseEventSubscriptionTest.cs b/Microservice.Api.Integration.Tests/Infrastructure/BaseEventSubscriptionTest.cs new file mode 100644 index 0000000..4f98e9e --- /dev/null +++ b/Microservice.Api.Integration.Tests/Infrastructure/BaseEventSubscriptionTest.cs @@ -0,0 +1,37 @@ +using Microservice.RabbitMQMessageBrokerExtension; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using NUnit.Framework; + +namespace Microservice.Api.Integration.Tests.Infrastructure +{ + public class BaseEventSubscriptionTest + { + private MockRepository _mockRepository; + protected internal Mock MessageBrokerClient; + protected internal WebApplicationFactory Factory; + + [OneTimeSetUp] + public void Setup() + { + _mockRepository = new MockRepository(MockBehavior.Strict); + MessageBrokerClient = _mockRepository.Create(MockBehavior.Loose); + Factory = new WebApplicationFactory() + + .WithWebHostBuilder(x => + { + x.ConfigureAppConfiguration((c, b) => { b.AddConfiguration(ConfigurationBuilderExtensions.GetConfigurationRoot()); }); + x.ConfigureTestServices(s => { s.AddTransient(_ => MessageBrokerClient.Object); }); + }) + ; + } + [TearDown] + public void TearDown() + { + _mockRepository.VerifyAll(); + } + } +} diff --git a/Microservice.Api.Integration.Tests/Infrastructure/ConfigurationBuilderExtensions.cs b/Microservice.Api.Integration.Tests/Infrastructure/ConfigurationBuilderExtensions.cs new file mode 100644 index 0000000..e160a40 --- /dev/null +++ b/Microservice.Api.Integration.Tests/Infrastructure/ConfigurationBuilderExtensions.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.Configuration; +using System.IO; + +namespace Microservice.Api.Integration.Tests.Infrastructure +{ + public static class ConfigurationBuilderExtensions + { + public static IConfigurationSection GetCustomSection(string section) + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true); + var config = configBuilder.Build(); + + return config.GetSection(section); + } + + public static IConfigurationRoot GetConfigurationRoot() + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true); + + return configBuilder.Build(); + } + } +} diff --git a/Microservice.Api.Integration.Tests/Infrastructure/HttpClientUtils.cs b/Microservice.Api.Integration.Tests/Infrastructure/HttpClientUtils.cs index 961b2da..e57d85d 100644 --- a/Microservice.Api.Integration.Tests/Infrastructure/HttpClientUtils.cs +++ b/Microservice.Api.Integration.Tests/Infrastructure/HttpClientUtils.cs @@ -17,7 +17,7 @@ public static Task PostAsJsonAsync( return httpClient.PostAsync(url, content); } - public static Task PutAsJsonAsync( + public static Task PutAsJsonAsync( this HttpClient httpClient, string url, T data) { var dataAsString = JsonConvert.SerializeObject(data); diff --git a/Microservice.Api.Integration.Tests/Microservice.Api.Integration.Tests.csproj b/Microservice.Api.Integration.Tests/Microservice.Api.Integration.Tests.csproj index b638fc7..33fabf0 100644 --- a/Microservice.Api.Integration.Tests/Microservice.Api.Integration.Tests.csproj +++ b/Microservice.Api.Integration.Tests/Microservice.Api.Integration.Tests.csproj @@ -9,9 +9,23 @@ + + + + + + Always + true + PreserveNewest + + + + + + @@ -19,13 +33,14 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Microservice.Api.Integration.Tests/OrderPlacedEventSubscriptionHandlerTest.cs b/Microservice.Api.Integration.Tests/OrderPlacedEventSubscriptionHandlerTest.cs new file mode 100644 index 0000000..68bcfea --- /dev/null +++ b/Microservice.Api.Integration.Tests/OrderPlacedEventSubscriptionHandlerTest.cs @@ -0,0 +1,55 @@ +using Microservice.Api.Integration.Tests.Infrastructure; +using Microservice.Db; +using Microservice.Db.EntityModels; +using Microservice.Logic.Orders.Events; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Microservice.Api.Integration.Tests +{ + [TestFixture] + public class OrderPlacedEventSubscriptionHandlerTest: BaseEventSubscriptionTest + { + [Test] + public async Task Given_OrderPlacedSubscriptionEvent_Expect_OrderResponse_With_OrderPlacedEvent_Published() + { + // Arrange + var order = new Order + { + Id = 2, + Name = "product zero two", + Quantity = 5 + }; + + var orderPlacedSubscriptionEvent = new OrderPlacedSubscriptionEvent + { + Id = order.Id, + Quantity = 2, + PersonId = 33 + }; + + Factory.Seed(db => + { + db.Clear(); + db.Orders.Add(order); + }); + + // Act + var handler = MessageBrokerClient.Invocations. + First(i => (string)i.Arguments[0] == "OrderPlaced") + .Arguments[2] as Func; + + await handler.Invoke(orderPlacedSubscriptionEvent); + + var dbContext = Factory.GetScopedService(); + + var orderResult = dbContext.Orders.Find(order.Id); + + // Assert handler + Assert.That(orderResult, Is.Not.Null); + Assert.That(orderResult.Quantity, Is.EqualTo(order.Quantity - orderPlacedSubscriptionEvent.Quantity)); + } + } +} diff --git a/Microservice.Api.Integration.Tests/OrdersControllerTests.cs b/Microservice.Api.Integration.Tests/OrdersControllerTests.cs index 90c3293..3cd9964 100644 --- a/Microservice.Api.Integration.Tests/OrdersControllerTests.cs +++ b/Microservice.Api.Integration.Tests/OrdersControllerTests.cs @@ -2,65 +2,63 @@ using Microservice.Db; using Microservice.Db.EntityModels; using Microservice.Logic.Orders.Commands; +using Microservice.Logic.Orders.Events; using Microservice.Logic.Orders.Responses; using Microsoft.AspNetCore.JsonPatch; -using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using NUnit.Framework; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Net.Http; using System.Threading.Tasks; namespace Microservice.Api.Integration.Tests { [TestFixture] - public class OrdersControllerTests + public class OrdersControllerTests : BaseControllerTest { - private WebApplicationFactory _factory; - private HttpClient _client; - private string PathBuilder(string extension) => $"api/{extension}"; - [OneTimeSetUp] - public void Setup() + public OrdersControllerTests() { - _factory = new WebApplicationFactory() - .WithWebHostBuilder(builder => - { - builder.ConfigureServices(services => - { - services.RemoveAll(typeof(DbContextOptions)); - services.RemoveAll(typeof(MicroserviceDbContext)); - services.AddDbContext(options => - { - options.UseInMemoryDatabase("TestInMemoryDatabase"); - }); - }); - }); - - _client = _factory.CreateClient(); + BaseRoute = "orders"; } [Test] - public async Task Given_CreateCustomerOrderCommand_Expect_OrderResponse() + public async Task Given_CreateOrderCommand_Which_Should_Publish_A_OrderCreatedEvent_Expect_OrderResponse_With_CreatedEvent_Published() { // Arrange - var createCustomerOrderCommand = new CreateOrderCommand("Testing command"); + const string topic = "OrderCreated"; + const string subscriptionId = "OrderCreated_IntegrationTest"; + var createCommand = new CreateOrderCommand("Keyboard", 5); - _factory.Seed(db => + Factory.Seed(db => { db.Clear(); }); // Act - var response = await _client.PostAsJsonAsync(PathBuilder("orders"), createCustomerOrderCommand); + var response = await Client.PostAsJsonAsync(GetBaseRoute(), createCommand); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Created)); var result = response.Content.Deserialize().Result; - - // Assert - StringAssert.AreEqualIgnoringCase(createCustomerOrderCommand.Name, result.Name); + var subscriptionResponse = await MessageBrokerClient.Subscribe( + topic, + subscriptionId, + AssertCallback, + c => c.UseBasicQos()); + + // Assert Create Response + Assert.That(result.Quantity, Is.EqualTo(createCommand.Quantity)); + StringAssert.AreEqualIgnoringCase(createCommand.Name, result.Name); + + // Assert Messagebus OrderCreatedEvent + Task AssertCallback(OrderCreatedEvent orderCreatedEvent) + { + var tcs = new TaskCompletionSource(); + + StringAssert.AreEqualIgnoringCase(orderCreatedEvent.Name, result.Name); + Assert.That(orderCreatedEvent.Quantity, Is.EqualTo(createCommand.Quantity)); + Assert.That(orderCreatedEvent.Id, Is.GreaterThan(0)); + tcs.SetResult(orderCreatedEvent); + return tcs.Task; + } } [Test] @@ -73,14 +71,14 @@ public async Task Given_GetOrderById_Expect_OrderResponse() Name = "Testing command" }; - _factory.Seed(db => + Factory.Seed(db => { db.Clear(); db.Orders.Add(order); }); // Act - var response = await _client.GetAsync(PathBuilder($"orders/{order.Id}")); + var response = await Client.GetAsync($"{GetBaseRoute()}/{order.Id}"); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); var result = response.Content.Deserialize().Result; @@ -111,13 +109,13 @@ public async Task Given_AllOrders_Expect_OrderResponses() Name = "Testing command" }; - _factory.Seed(db => + Factory.Seed(db => { db.Clear(); db.Orders.AddRange(order1, order2, order3); }); - var response = await _client.GetAsync(PathBuilder($"orders")); + var response = await Client.GetAsync($"{GetBaseRoute()}"); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); var result = response.Content.Deserialize>().Result; @@ -126,10 +124,12 @@ public async Task Given_AllOrders_Expect_OrderResponses() } [Test] - public async Task Given_PatchOnOrder_Expact_Updated_OrderResponse() + public async Task Given_PatchOrder_Which_Should_Publish_OrderUpdatedEvent_Expect_Updated_OrderResponse_With_UpdatedEvent_Published() { // Arrange - var origionalOrder = new Order + const string topic = "Order"; + const string subscriptionId = "OrderUpdated"; + var originationOrder = new Order { Id = 1, Name = "product zero one" @@ -140,30 +140,90 @@ public async Task Given_PatchOnOrder_Expact_Updated_OrderResponse() var operations = patchDoc.Operations.ToList(); - _factory.Seed(db => + Factory.Seed(db => { db.Clear(); - db.Orders.Add(origionalOrder); + db.Orders.Add(originationOrder); }); // Act - var response = await _client.PatchAsJsonAsync(PathBuilder($"orders/update/{origionalOrder.Id}/77"), patchDoc); + var response = await Client.PatchAsJsonAsync($"{GetBaseRoute()}/update/{originationOrder.Id}/77", patchDoc); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); var result = response.Content.Deserialize().Result; + var subscriptionResponse = await MessageBrokerClient.Subscribe( + topic, + subscriptionId, + AssertCallback, + c => c.UseBasicQos()); - // Assert + // Assert Patch Operation StringAssert.AreEqualIgnoringCase($"PROD: zero one", result.Name); - } -// [Test] -// public async Task Given_CreateOrder_Publish_OrderCreatedEvent_Expect_OrderCreatedEvent_From_MessageBroker() -// { -// -// } + // Assert OrderUpdatedEvent + Task AssertCallback(OrderUpdatedEvent updatedEvent) + { + var tcs = new TaskCompletionSource(); + + StringAssert.AreEqualIgnoringCase($"PROD: zero one", updatedEvent.Name); + Assert.That(updatedEvent.Quantity, Is.EqualTo(result.Quantity)); + Assert.That(updatedEvent.Id, Is.EqualTo(result.Id)); + + tcs.SetResult(updatedEvent); + return tcs.Task; + } + } - private static string GetMessage() + [Test] + public async Task Given_PutOrderPlacedCommand_Which_Should_Publish_A_PutOrderPlacedEvent_Expect_OrderResponse_With_OrderPlacedEvent_Published() { - return "info: Hello World!"; + // Arrange + const string topic = "OrderPlacedUpdated"; + const string subscriptionId = "OrderPlacedUpdated_IntegrationTest"; + var command = new PutOrderPlacedCommand(1, 5, 2); + var order = new Order + { + Id = 1, + Name = "product zero one", + Quantity = 10 + }; + + Factory.Seed(db => + { + db.Clear(); + db.Orders.Add(order); + }); + + // Act + var response = await Client.PutAsJsonAsync($"{GetBaseRoute()}/place", command); + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + var result = response.Content.Deserialize().Result; + + var subscriptionResponse = await MessageBrokerClient.Subscribe( + topic, + subscriptionId, + AssertCallback, + c => c.UseBasicQos()); + + // Assert PutOrderPlaced Response + Assert.That(result.Quantity, Is.EqualTo(8)); + Assert.That(result.Id, Is.EqualTo(order.Id)); + StringAssert.AreEqualIgnoringCase(order.Name, result.Name); + + // Assert Messagebus OrderCreatedEvent + Task AssertCallback(OrderPlacedEvent orderPlacedEvent) + { + var tcs = new TaskCompletionSource(); + + Assert.That(orderPlacedEvent.QuantityBeforeReduction, Is.EqualTo(order.Quantity)); + Assert.That(orderPlacedEvent.Quantity, Is.EqualTo(result.Quantity)); + Assert.That(result.Id, Is.EqualTo(order.Id)); + StringAssert.AreEqualIgnoringCase(order.Name, result.Name); + + tcs.SetResult(orderPlacedEvent); + return tcs.Task; + } } + + } } diff --git a/Microservice.Api.Integration.Tests/appsettings.json b/Microservice.Api.Integration.Tests/appsettings.json new file mode 100644 index 0000000..082c4c1 --- /dev/null +++ b/Microservice.Api.Integration.Tests/appsettings.json @@ -0,0 +1,20 @@ +{ + "RabbitMessageBrokerSettings": { + "Host": "localhost", + "Port": 5672, + "UserName": "guest", + "Password": "guest", + "VirtualHost": "/", + "MaxConnectionRetries": 1000, + "ConnectionAttempBackoffFactor": 1.5, + "ConnectionAttempMaxBackoff": 10000, + "PublishConnectionTimeoutInSeconds": 10 + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/Microservice.Api/Conficuration/CorsServiceCollectionExtensions.cs b/Microservice.Api/Configuration/CorsServiceCollectionExtensions.cs similarity index 59% rename from Microservice.Api/Conficuration/CorsServiceCollectionExtensions.cs rename to Microservice.Api/Configuration/CorsServiceCollectionExtensions.cs index f6970f6..75d316f 100644 --- a/Microservice.Api/Conficuration/CorsServiceCollectionExtensions.cs +++ b/Microservice.Api/Configuration/CorsServiceCollectionExtensions.cs @@ -1,12 +1,10 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; -namespace Microservice.Api.Conficuration +namespace Microservice.Api.Configuration { public static class CorsServiceCollectionExtensions { - public static IServiceCollection AddCors(this IServiceCollection services, - IConfiguration config) + public static IServiceCollection AddCorsRules(this IServiceCollection services) { services.AddCors(options => options.AddPolicy("Default", builder => { diff --git a/Microservice.Api/Configuration/MessageBrokerCollectionExtensions.cs b/Microservice.Api/Configuration/MessageBrokerCollectionExtensions.cs new file mode 100644 index 0000000..4bf2b16 --- /dev/null +++ b/Microservice.Api/Configuration/MessageBrokerCollectionExtensions.cs @@ -0,0 +1,35 @@ +using Microservice.Logic.Orders.EventPublishers; +using Microservice.Logic.Orders.Events; +using Microservice.Logic.Orders.EventSubscriptionHandlers; +using Microservice.RabbitMessageBrokerHelpers.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microservice.Api.Configuration +{ + public static class MessageBrokerCollectionExtensions + { + public static IServiceCollection AddMessageBrokerCustomSubscriptions(this IServiceCollection services) + { + services + .AddMessageBrokerSubscriptions(x => + x + .UsePool("Orders") + .Subscribe("OrderPlaced") + ) + ; + + return services; + } + + public static IServiceCollection AddMessageBrokerCustomPublishers(this IServiceCollection services) + { + services + .AddTransient() + .AddTransient() + .AddTransient() + ; + + return services; + } + } +} diff --git a/Microservice.Api/Controllers/OrdersController.cs b/Microservice.Api/Controllers/OrdersController.cs index 7b44ffc..20eaa90 100644 --- a/Microservice.Api/Controllers/OrdersController.cs +++ b/Microservice.Api/Controllers/OrdersController.cs @@ -1,11 +1,10 @@ using MediatR; -using Microsoft.AspNetCore.JsonPatch; -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; using Microservice.Logic.Orders.Commands; using Microservice.Logic.Orders.Models; using Microservice.Logic.Orders.Queries; -using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.JsonPatch; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; namespace Microservice.Api.Controllers { @@ -44,11 +43,18 @@ public async Task CreateOrder([FromBody] CreateOrderCommand comma } [HttpPatch("update/{id}/{personId}")] - public async Task PatchOrder([FromRoute]long id,[FromRoute] long personId, [FromBody] JsonPatchDocument patchModel) + public async Task PatchOrder([FromRoute]long id, [FromRoute] long personId, [FromBody] JsonPatchDocument patchModel) { - var command = new PatchOrderCommand(id,personId,patchModel); + var command = new PatchOrderCommand(id, personId, patchModel); var result = await _mediator.Send(command); return result != null ? (IActionResult)Ok(result) : NotFound(); } + + [HttpPut("place")] + public async Task PlaceOrder([FromBody]PutOrderPlacedCommand orderPlacedCommand) + { + var result = await _mediator.Send(orderPlacedCommand); + return result != null ? (IActionResult)Ok(result) : NotFound(); + } } } diff --git a/Microservice.Api/MessageBus/OrderSubscriptions/Subscriptions/OrderPlacedSubscription.cs b/Microservice.Api/MessageBus/OrderSubscriptions/Subscriptions/OrderPlacedSubscription.cs deleted file mode 100644 index f9297ca..0000000 --- a/Microservice.Api/MessageBus/OrderSubscriptions/Subscriptions/OrderPlacedSubscription.cs +++ /dev/null @@ -1,32 +0,0 @@ -using MediatR; -using Microservice.RabbitMessageBroker; -using Microsoft.Extensions.Hosting; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microservice.Api.MessageBus.OrderSubscriptions.Subscriptions -{ - public class OrderPlacedSubscription: IHostedService - { - private readonly IRabbitMessageBrokerClient _messageBrokerClient; - private readonly IServiceProvider _serviceProvider; - private readonly IMediator _mediator; - public OrderPlacedSubscription(IRabbitMessageBrokerClient messageBrokerClient, IServiceProvider serviceProvider, IMediator mediator) - { - _messageBrokerClient = messageBrokerClient; - _serviceProvider = serviceProvider; - _mediator = mediator; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - } -} diff --git a/Microservice.Api/Microservice.Api.csproj b/Microservice.Api/Microservice.Api.csproj index cfff474..4012670 100644 --- a/Microservice.Api/Microservice.Api.csproj +++ b/Microservice.Api/Microservice.Api.csproj @@ -10,6 +10,7 @@ + @@ -22,8 +23,8 @@ - + - + diff --git a/Microservice.Api/Startup.cs b/Microservice.Api/Startup.cs index 38da4eb..5e35b4c 100644 --- a/Microservice.Api/Startup.cs +++ b/Microservice.Api/Startup.cs @@ -1,11 +1,13 @@ using FluentValidation.AspNetCore; using MediatR; +using Microservice.Api.Configuration; using Microservice.Api.Filters; using Microservice.Db.Configuration; -using Microservice.HanfireWithRedisBackingStore.Configuration; +using Microservice.HangfireBackgroundJobServer.Configuration; +using Microservice.Logic.BackgroundProcessing; using Microservice.Logic.Configuration; using Microservice.Logic.Orders.Validators; -using Microservice.RabbitMessageBroker.Configuration; +using Microservice.RabbitMQMessageBrokerExtension.Configuration; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -26,7 +28,7 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services - .AddCors() + .AddCorsRules() .AddControllers() .AddNewtonsoftJson() ; @@ -40,8 +42,11 @@ public void ConfigureServices(IServiceCollection services) .AddDatabase(_configuration.GetConnectionString("Database")) .AddLogic(_configuration) .AddMediatR(typeof(LogicServiceCollectionExtensions).Assembly) - .AddMessageBroker(_configuration.GetSection("MessageBrokerSettings")) - .AddBackgroundJobServer(_configuration.GetSection("BackgroundJobServerSettings")) + .AddRabbitMqMessageBroker(_configuration.GetSection("MessageBrokerSettings")) + .AddHangfireBackgroundJobServer(_configuration.GetSection("BackgroundJobServerSettings")) + .AddMessageBrokerCustomSubscriptions() + .AddMessageBrokerCustomPublishers() + .AddBackgroundProcessing(_configuration) ; } diff --git a/Microservice.Api/appsettings.json b/Microservice.Api/appsettings.json index 06806d0..43d7449 100644 --- a/Microservice.Api/appsettings.json +++ b/Microservice.Api/appsettings.json @@ -16,6 +16,16 @@ "BackgroundJobServerSettings": { "ConnectionString": "Host=localhost;Username=postgres;Password=root;Database=microservice.db;Port=5433" }, + "BackgroundProcessing": { + "Heartbeat": { + "Enabled": true, + "Cron": "* * * * *" + }, + "ReStockZeroQuantityItems": { + "Enabled": true, + "Cron": "* * */1 * *" + } + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/Microservice.Db/Configuration/DatabaseServiceCollectionExtensions.cs b/Microservice.Db/Configuration/DatabaseServiceCollectionExtensions.cs index 18fa5f6..acbff89 100644 --- a/Microservice.Db/Configuration/DatabaseServiceCollectionExtensions.cs +++ b/Microservice.Db/Configuration/DatabaseServiceCollectionExtensions.cs @@ -7,7 +7,8 @@ public static class DatabaseServiceCollectionExtensions { public static IServiceCollection AddDatabase(this IServiceCollection services, string dbConnectionString) { - services.AddDbContext(o => o.UseNpgsql(dbConnectionString)); + services + .AddDbContext(o => o.UseNpgsql(dbConnectionString)); return services; } } diff --git a/Microservice.Db/EntityModels/Order.cs b/Microservice.Db/EntityModels/Order.cs index 98d044c..61f2d25 100644 --- a/Microservice.Db/EntityModels/Order.cs +++ b/Microservice.Db/EntityModels/Order.cs @@ -4,5 +4,6 @@ public class Order { public long Id { get; set; } public string Name { get; set; } + public int Quantity { get; set; } } } diff --git a/Microservice.Db/Migrations/20200504043519_AddOrderQuantityProperty.Designer.cs b/Microservice.Db/Migrations/20200504043519_AddOrderQuantityProperty.Designer.cs new file mode 100644 index 0000000..a827c8c --- /dev/null +++ b/Microservice.Db/Migrations/20200504043519_AddOrderQuantityProperty.Designer.cs @@ -0,0 +1,43 @@ +// +using Microservice.Db; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Microservice.Db.Migrations +{ + [DbContext(typeof(MicroserviceDbContext))] + [Migration("20200504043519_AddOrderQuantityProperty")] + partial class AddOrderQuantityProperty + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("Microservice.Db.EntityModels.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Microservice.Db/Migrations/20200504043519_AddOrderQuantityProperty.cs b/Microservice.Db/Migrations/20200504043519_AddOrderQuantityProperty.cs new file mode 100644 index 0000000..5b699ba --- /dev/null +++ b/Microservice.Db/Migrations/20200504043519_AddOrderQuantityProperty.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microservice.Db.Migrations +{ + public partial class AddOrderQuantityProperty : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Quantity", + table: "Orders", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Quantity", + table: "Orders"); + } + } +} diff --git a/Microservice.Db/Migrations/MicroserviceDbContextModelSnapshot.cs b/Microservice.Db/Migrations/MicroserviceDbContextModelSnapshot.cs index 97600d9..eb35d4d 100644 --- a/Microservice.Db/Migrations/MicroserviceDbContextModelSnapshot.cs +++ b/Microservice.Db/Migrations/MicroserviceDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.0") + .HasAnnotation("ProductVersion", "3.1.3") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("Microservice.Db.EntityModels.Order", b => @@ -28,6 +28,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .HasColumnType("text"); + b.Property("Quantity") + .HasColumnType("integer"); + b.HasKey("Id"); b.ToTable("Orders"); diff --git a/Microservice.HangfireBackgroundJobServer/Configuration/BackgroundJobServerServiceCollectionExtensions.cs b/Microservice.HangfireBackgroundJobServer/Configuration/BackgroundJobServerServiceCollectionExtensions.cs index 42d2629..9b71fc5 100644 --- a/Microservice.HangfireBackgroundJobServer/Configuration/BackgroundJobServerServiceCollectionExtensions.cs +++ b/Microservice.HangfireBackgroundJobServer/Configuration/BackgroundJobServerServiceCollectionExtensions.cs @@ -1,20 +1,18 @@ using Hangfire; using Hangfire.Dashboard; using Hangfire.PostgreSql; -using Microservice.HanfireWithRedisBackingStore.Infrastructure; +using Microservice.HangfireBackgroundJobServer.Infrastructure; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using System; using System.Collections.Generic; -namespace Microservice.HanfireWithRedisBackingStore.Configuration +namespace Microservice.HangfireBackgroundJobServer.Configuration { public static class BackgroundJobServerServiceCollectionExtensions { - public static IServiceCollection AddBackgroundJobServer(this IServiceCollection services, + public static IServiceCollection AddHangfireBackgroundJobServer(this IServiceCollection services, IConfiguration config) { var settings = new BackgroundJobServerSettings(); @@ -37,7 +35,7 @@ public static IServiceCollection AddBackgroundJobServer(this IServiceCollection services .AddTransient(); - + return services; } diff --git a/Microservice.HangfireBackgroundJobServer/Configuration/BackgroundJobServerSettings.cs b/Microservice.HangfireBackgroundJobServer/Configuration/BackgroundJobServerSettings.cs index 9252c3b..d50b2c9 100644 --- a/Microservice.HangfireBackgroundJobServer/Configuration/BackgroundJobServerSettings.cs +++ b/Microservice.HangfireBackgroundJobServer/Configuration/BackgroundJobServerSettings.cs @@ -1,4 +1,4 @@ -namespace Microservice.HanfireWithRedisBackingStore.Configuration +namespace Microservice.HangfireBackgroundJobServer.Configuration { public class BackgroundJobServerSettings { diff --git a/Microservice.HangfireBackgroundJobServer/Extensions/JobConfig.cs b/Microservice.HangfireBackgroundJobServer/Extensions/JobConfig.cs new file mode 100644 index 0000000..5352de0 --- /dev/null +++ b/Microservice.HangfireBackgroundJobServer/Extensions/JobConfig.cs @@ -0,0 +1,8 @@ +namespace Microservice.HangfireBackgroundJobServer.Extensions +{ + public class JobConfig + { + public string Cron { get; set; } + public bool Enabled { get; set; } + } +} diff --git a/Microservice.HangfireBackgroundJobServer/Extensions/RecurringJobConfig.cs b/Microservice.HangfireBackgroundJobServer/Extensions/RecurringJobConfig.cs new file mode 100644 index 0000000..142619b --- /dev/null +++ b/Microservice.HangfireBackgroundJobServer/Extensions/RecurringJobConfig.cs @@ -0,0 +1,8 @@ +namespace Microservice.HangfireBackgroundJobServer.Extensions +{ + public class RecurringJobConfig + { + public bool Enabled { get; set; } + public string Cron { get; set; } + } +} diff --git a/Microservice.HangfireBackgroundJobServer/IRecurringJob.cs b/Microservice.HangfireBackgroundJobServer/IRecurringJob.cs index 626f0d6..f735d87 100644 --- a/Microservice.HangfireBackgroundJobServer/IRecurringJob.cs +++ b/Microservice.HangfireBackgroundJobServer/IRecurringJob.cs @@ -1,7 +1,7 @@ -using Hangfire; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Hangfire; -namespace Microservice.HanfireWithRedisBackingStore +namespace Microservice.HangfireBackgroundJobServer { public interface IRecurringJob { diff --git a/Microservice.HangfireBackgroundJobServer/Infrastructure/BackgroundProcessActivator.cs b/Microservice.HangfireBackgroundJobServer/Infrastructure/BackgroundProcessActivator.cs index bbdfad7..a6d3ab7 100644 --- a/Microservice.HangfireBackgroundJobServer/Infrastructure/BackgroundProcessActivator.cs +++ b/Microservice.HangfireBackgroundJobServer/Infrastructure/BackgroundProcessActivator.cs @@ -2,7 +2,7 @@ using Hangfire; using Microsoft.Extensions.DependencyInjection; -namespace Microservice.HanfireWithRedisBackingStore.Infrastructure +namespace Microservice.HangfireBackgroundJobServer.Infrastructure { public class BackgroundProcessActivator : JobActivator { diff --git a/Microservice.HangfireBackgroundJobServer/Infrastructure/BackgroundProcessingClient.cs b/Microservice.HangfireBackgroundJobServer/Infrastructure/BackgroundProcessingClient.cs index 420b60d..c18c8ca 100644 --- a/Microservice.HangfireBackgroundJobServer/Infrastructure/BackgroundProcessingClient.cs +++ b/Microservice.HangfireBackgroundJobServer/Infrastructure/BackgroundProcessingClient.cs @@ -1,18 +1,23 @@ using Hangfire; +using Microservice.HangfireBackgroundJobServer.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using System; using System.Linq.Expressions; using System.Threading.Tasks; +using Hangfire.PostgreSql; -namespace Microservice.HanfireWithRedisBackingStore.Infrastructure +namespace Microservice.HangfireBackgroundJobServer.Infrastructure { public class BackgroundProcessingClient : IBackgroundProcessingClient { private readonly IServiceProvider _serviceProvider; + private readonly IOptions _options; - public BackgroundProcessingClient(IServiceProvider serviceProvider) + public BackgroundProcessingClient(IServiceProvider serviceProvider, IOptions options) { _serviceProvider = serviceProvider; + _options = options; } public string Enqueue(Expression action) @@ -53,6 +58,7 @@ public void ConfigureRecurringJob( where T : IRecurringJob { var jobRunner = _serviceProvider.CreateScope().ServiceProvider.GetService(); + JobStorage.Current = new PostgreSqlStorage(_options.Value.ConnectionString); if (jobRunner == null) throw new Exception("Could not activate recurring job. Ensure it is configured on the service provider"); diff --git a/Microservice.HangfireBackgroundJobServer/Infrastructure/IBackgroundProcessingClient.cs b/Microservice.HangfireBackgroundJobServer/Infrastructure/IBackgroundProcessingClient.cs index a522471..41fc741 100644 --- a/Microservice.HangfireBackgroundJobServer/Infrastructure/IBackgroundProcessingClient.cs +++ b/Microservice.HangfireBackgroundJobServer/Infrastructure/IBackgroundProcessingClient.cs @@ -2,7 +2,7 @@ using System.Linq.Expressions; using System.Threading.Tasks; -namespace Microservice.HanfireWithRedisBackingStore.Infrastructure +namespace Microservice.HangfireBackgroundJobServer.Infrastructure { public interface IBackgroundProcessingClient { @@ -11,7 +11,10 @@ public interface IBackgroundProcessingClient Task Run(Expression> action); void RemoveRecurringJobIfExists(string recurringJobId); - void ConfigureRecurringJob(string recurringJobId, string cronExpression, bool enabled = true) + void ConfigureRecurringJob( + string recurringJobId, + string cronExpression, + bool enabled = true) where T : IRecurringJob; } } diff --git a/Microservice.Logic/BackgroundProcessing/BackgroundProcessingServiceCollectionExtensions.cs b/Microservice.Logic/BackgroundProcessing/BackgroundProcessingServiceCollectionExtensions.cs new file mode 100644 index 0000000..514b6de --- /dev/null +++ b/Microservice.Logic/BackgroundProcessing/BackgroundProcessingServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microservice.Logic.BackgroundProcessing +{ + public static class BackgroundProcessingServiceCollectionExtensions + { + public static IServiceCollection AddBackgroundProcessing(this IServiceCollection container, IConfiguration config) + { + container + .Configure(config.GetSection("BackgroundProcessing")) + .AddHostedService() + .AddTransient() + .AddTransient() + ; + + return container; + } + } +} diff --git a/Microservice.Logic/BackgroundProcessing/HeartbeatRecurringJob.cs b/Microservice.Logic/BackgroundProcessing/HeartbeatRecurringJob.cs new file mode 100644 index 0000000..2312d72 --- /dev/null +++ b/Microservice.Logic/BackgroundProcessing/HeartbeatRecurringJob.cs @@ -0,0 +1,33 @@ +using Hangfire; +using Microservice.HangfireBackgroundJobServer; +using Microsoft.Extensions.Logging; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace Microservice.Logic.BackgroundProcessing +{ + public class HeartbeatRecurringJob : IRecurringJob + { + private readonly ILogger _logger; + public HeartbeatRecurringJob(ILogger logger) + { + _logger = logger; + } + + public Task Run(IJobCancellationToken cancellationToken) + { + var sw = new Stopwatch(); + sw.Start(); + + _logger.LogInformation("Heartbeat completed, {@details}", new + { + HeartbeatCompleted = new + { + Took = sw.ElapsedMilliseconds + } + }); + + return Task.CompletedTask; + } + } +} diff --git a/Microservice.Logic/BackgroundProcessing/JobScheduler.cs b/Microservice.Logic/BackgroundProcessing/JobScheduler.cs new file mode 100644 index 0000000..7b777b5 --- /dev/null +++ b/Microservice.Logic/BackgroundProcessing/JobScheduler.cs @@ -0,0 +1,45 @@ +using Microservice.HangfireBackgroundJobServer.Infrastructure; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using System.Threading; +using System.Threading.Tasks; + +namespace Microservice.Logic.BackgroundProcessing +{ + public class JobScheduler : IHostedService + { + private readonly IBackgroundProcessingClient _backgroundProcessingClient; + private readonly IOptionsMonitor _settings; + + public JobScheduler( + IBackgroundProcessingClient backgroundProcessingClient, + IOptionsMonitor settings) + { + _backgroundProcessingClient = backgroundProcessingClient; + _settings = settings; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + + _backgroundProcessingClient.ConfigureRecurringJob( + "Heartbeat", + _settings.CurrentValue.Heartbeat.Cron, + _settings.CurrentValue.Heartbeat.Enabled + ); + + _backgroundProcessingClient.ConfigureRecurringJob( + "ReStockZeroQuantityItems", + _settings.CurrentValue.ReStockZeroQuantityItems.Cron, + _settings.CurrentValue.ReStockZeroQuantityItems.Enabled + ); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/Microservice.Logic/BackgroundProcessing/JobSettings.cs b/Microservice.Logic/BackgroundProcessing/JobSettings.cs new file mode 100644 index 0000000..085a51b --- /dev/null +++ b/Microservice.Logic/BackgroundProcessing/JobSettings.cs @@ -0,0 +1,12 @@ +using Microservice.HangfireBackgroundJobServer.Extensions; + +namespace Microservice.Logic.BackgroundProcessing +{ + public class JobSettings + { + public RecurringJobConfig Heartbeat { get; set; } + public RecurringJobConfig CapStockItemsToFixedQuantityRecurringJob { get; set; } + + public ReStockZeroQuantityItemsJobConfig ReStockZeroQuantityItems { get; set; } + } +} diff --git a/Microservice.Logic/BackgroundProcessing/ReStockZeroQuantityItemsJobConfig.cs b/Microservice.Logic/BackgroundProcessing/ReStockZeroQuantityItemsJobConfig.cs new file mode 100644 index 0000000..1edfcde --- /dev/null +++ b/Microservice.Logic/BackgroundProcessing/ReStockZeroQuantityItemsJobConfig.cs @@ -0,0 +1,9 @@ +using Microservice.HangfireBackgroundJobServer.Extensions; + +namespace Microservice.Logic.BackgroundProcessing +{ + public class ReStockZeroQuantityItemsJobConfig : RecurringJobConfig + { + public int ZeroQuantityLimit { get; set; } + } +} \ No newline at end of file diff --git a/Microservice.Logic/BackgroundProcessing/ReStockZeroQuantityItemsRecurringJob.cs b/Microservice.Logic/BackgroundProcessing/ReStockZeroQuantityItemsRecurringJob.cs new file mode 100644 index 0000000..238ff9f --- /dev/null +++ b/Microservice.Logic/BackgroundProcessing/ReStockZeroQuantityItemsRecurringJob.cs @@ -0,0 +1,24 @@ +using Hangfire; +using MediatR; +using Microservice.HangfireBackgroundJobServer; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; + +namespace Microservice.Logic.BackgroundProcessing +{ + public class ReStockZeroQuantityItemsRecurringJob : IRecurringJob + { + private readonly ILogger _logger; + private readonly IMediator _mediator; + public ReStockZeroQuantityItemsRecurringJob(ILogger logger, IMediator mediator) + { + _logger = logger; + _mediator = mediator; + } + + public Task Run(IJobCancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/Microservice.Logic/Microservice.Logic.csproj b/Microservice.Logic/Microservice.Logic.csproj index e447d6a..9e3e999 100644 --- a/Microservice.Logic/Microservice.Logic.csproj +++ b/Microservice.Logic/Microservice.Logic.csproj @@ -6,12 +6,12 @@ - + - + diff --git a/Microservice.Logic/Orders/Commands/CreateOrderCommand.cs b/Microservice.Logic/Orders/Commands/CreateOrderCommand.cs index 51556d7..66c6805 100644 --- a/Microservice.Logic/Orders/Commands/CreateOrderCommand.cs +++ b/Microservice.Logic/Orders/Commands/CreateOrderCommand.cs @@ -5,11 +5,13 @@ namespace Microservice.Logic.Orders.Commands { public class CreateOrderCommand : IRequest { - public string Name { get; set; } + public string Name { get; } + public int Quantity { get; } - public CreateOrderCommand(string name) + public CreateOrderCommand(string name, int quantity) { Name = name; + Quantity = quantity; } } } diff --git a/Microservice.Logic/Orders/Commands/PatchOrderCommand.cs b/Microservice.Logic/Orders/Commands/PatchOrderCommand.cs index 1fce294..1b12441 100644 --- a/Microservice.Logic/Orders/Commands/PatchOrderCommand.cs +++ b/Microservice.Logic/Orders/Commands/PatchOrderCommand.cs @@ -9,9 +9,10 @@ public class PatchOrderCommand : IRequest { public long OrderId { get; } public long PersonId { get; } + public JsonPatchDocument JsonPatchDocument { get; set; } - public PatchOrderCommand(long orderId, long personId,JsonPatchDocument jsonPatchDocument ) + public PatchOrderCommand(long orderId, long personId, JsonPatchDocument jsonPatchDocument) { OrderId = orderId; PersonId = personId; diff --git a/Microservice.Logic/Orders/Commands/PutOrderPlacedCommand.cs b/Microservice.Logic/Orders/Commands/PutOrderPlacedCommand.cs new file mode 100644 index 0000000..076007a --- /dev/null +++ b/Microservice.Logic/Orders/Commands/PutOrderPlacedCommand.cs @@ -0,0 +1,19 @@ +using MediatR; +using Microservice.Logic.Orders.Responses; + +namespace Microservice.Logic.Orders.Commands +{ + public class PutOrderPlacedCommand : IRequest + { + public long Id { get; } + public int Quantity { get; } + public long PersonId { get; } + + public PutOrderPlacedCommand(long id, long personId, int quantity) + { + PersonId = personId; + Quantity = quantity; + Id = id; + } + } +} diff --git a/Microservice.Logic/Orders/EventPublishers/IOrderCreatedEventPublisher.cs b/Microservice.Logic/Orders/EventPublishers/IOrderCreatedEventPublisher.cs new file mode 100644 index 0000000..91f5fe4 --- /dev/null +++ b/Microservice.Logic/Orders/EventPublishers/IOrderCreatedEventPublisher.cs @@ -0,0 +1,10 @@ +using Microservice.Logic.Orders.Events; +using System.Threading.Tasks; + +namespace Microservice.Logic.Orders.EventPublishers +{ + public interface IOrderCreatedEventPublisher + { + Task Publish(OrderCreatedEvent eventModel); + } +} diff --git a/Microservice.Logic/Orders/EventPublishers/IOrderPatchedEventPublisher.cs b/Microservice.Logic/Orders/EventPublishers/IOrderPatchedEventPublisher.cs new file mode 100644 index 0000000..119d779 --- /dev/null +++ b/Microservice.Logic/Orders/EventPublishers/IOrderPatchedEventPublisher.cs @@ -0,0 +1,10 @@ +using Microservice.Logic.Orders.Events; +using System.Threading.Tasks; + +namespace Microservice.Logic.Orders.EventPublishers +{ + public interface IOrderPatchedEventPublisher + { + Task Publish(OrderUpdatedEvent eventModel); + } +} diff --git a/Microservice.Logic/Orders/EventPublishers/IOrderPlacedEventPublisher.cs b/Microservice.Logic/Orders/EventPublishers/IOrderPlacedEventPublisher.cs new file mode 100644 index 0000000..3fae5fe --- /dev/null +++ b/Microservice.Logic/Orders/EventPublishers/IOrderPlacedEventPublisher.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Microservice.Logic.Orders.Events; + +namespace Microservice.Logic.Orders.EventPublishers +{ + public interface IOrderPlacedEventPublisher + { + Task Publish(OrderPlacedEvent orderPlacedEvent); + } +} diff --git a/Microservice.Logic/Orders/EventPublishers/OrderCreatedEventPublisher.cs b/Microservice.Logic/Orders/EventPublishers/OrderCreatedEventPublisher.cs new file mode 100644 index 0000000..14f8809 --- /dev/null +++ b/Microservice.Logic/Orders/EventPublishers/OrderCreatedEventPublisher.cs @@ -0,0 +1,20 @@ +using Microservice.Logic.Orders.Events; +using Microservice.RabbitMessageBrokerHelpers.Handlers; +using Microservice.RabbitMQMessageBrokerExtension; +using System.Threading.Tasks; + +namespace Microservice.Logic.Orders.EventPublishers +{ + public class OrderCreatedEventPublisher : EventPublisher,IOrderCreatedEventPublisher + { + private IRabbitMessageBrokerClient BrokerClient { get; } + + public OrderCreatedEventPublisher(IRabbitMessageBrokerClient brokerClient) + { + BrokerClient = brokerClient; + } + + public override async Task Publish(OrderCreatedEvent eventModel) + => await BrokerClient.Publish("OrderCreated", eventModel); + } +} diff --git a/Microservice.Logic/Orders/EventPublishers/OrderPatchedEventPublisher.cs b/Microservice.Logic/Orders/EventPublishers/OrderPatchedEventPublisher.cs new file mode 100644 index 0000000..e0e2551 --- /dev/null +++ b/Microservice.Logic/Orders/EventPublishers/OrderPatchedEventPublisher.cs @@ -0,0 +1,19 @@ +using Microservice.Logic.Orders.Events; +using Microservice.RabbitMessageBrokerHelpers.Handlers; +using Microservice.RabbitMQMessageBrokerExtension; +using System.Threading.Tasks; + +namespace Microservice.Logic.Orders.EventPublishers +{ + public class OrderPatchedEventPublisher : EventPublisher,IOrderPatchedEventPublisher + { + private IRabbitMessageBrokerClient BrokerClient { get; } + + public OrderPatchedEventPublisher(IRabbitMessageBrokerClient brokerClient) + { + BrokerClient = brokerClient; + } + public override async Task Publish(OrderUpdatedEvent eventModel) + => await BrokerClient.Publish("OrderUpdated", eventModel); + } +} diff --git a/Microservice.Logic/Orders/EventPublishers/OrderPlacedEventPublisher.cs b/Microservice.Logic/Orders/EventPublishers/OrderPlacedEventPublisher.cs new file mode 100644 index 0000000..ba00748 --- /dev/null +++ b/Microservice.Logic/Orders/EventPublishers/OrderPlacedEventPublisher.cs @@ -0,0 +1,20 @@ +using Microservice.Logic.Orders.Events; +using Microservice.RabbitMessageBrokerHelpers.Handlers; +using Microservice.RabbitMQMessageBrokerExtension; +using System.Threading.Tasks; + +namespace Microservice.Logic.Orders.EventPublishers +{ + public class OrderPlacedEventPublisher : EventPublisher, IOrderPlacedEventPublisher + { + private IRabbitMessageBrokerClient BrokerClient { get; } + + public OrderPlacedEventPublisher(IRabbitMessageBrokerClient brokerClient) + { + BrokerClient = brokerClient; + } + + public override async Task Publish(OrderPlacedEvent orderPlacedEvent) + => await BrokerClient.Publish("OrderPlacedUpdated", orderPlacedEvent); + } +} diff --git a/Microservice.Logic/Orders/EventSubscriptionHandlers/OrderPlacedEventSubscriptionHandler.cs b/Microservice.Logic/Orders/EventSubscriptionHandlers/OrderPlacedEventSubscriptionHandler.cs new file mode 100644 index 0000000..65d508c --- /dev/null +++ b/Microservice.Logic/Orders/EventSubscriptionHandlers/OrderPlacedEventSubscriptionHandler.cs @@ -0,0 +1,22 @@ +using MediatR; +using Microservice.Logic.Orders.Events; +using Microservice.RabbitMessageBrokerHelpers.Handlers; +using System.Threading.Tasks; + +namespace Microservice.Logic.Orders.EventSubscriptionHandlers +{ + public class OrderPlacedEventSubscriptionHandler : MessageBrokerSubscriptionHandler + { + private readonly IMediator _mediator; + + public OrderPlacedEventSubscriptionHandler(IMediator mediator) + { + _mediator = mediator; + } + + public override async Task Handle(OrderPlacedSubscriptionEvent eventModel) + { + await _mediator.Send(eventModel.ToPutPlacedOrderCommand()); + } + } +} diff --git a/Microservice.Logic/Orders/Events/OrderCreatedEvent.cs b/Microservice.Logic/Orders/Events/OrderCreatedEvent.cs index 210bd6f..a8be5d4 100644 --- a/Microservice.Logic/Orders/Events/OrderCreatedEvent.cs +++ b/Microservice.Logic/Orders/Events/OrderCreatedEvent.cs @@ -4,5 +4,6 @@ public class OrderCreatedEvent { public long Id { get; set; } public string Name { get; set; } + public int Quantity { get; set; } } } diff --git a/Microservice.Logic/Orders/Events/OrderPlacedEvent.cs b/Microservice.Logic/Orders/Events/OrderPlacedEvent.cs new file mode 100644 index 0000000..382d438 --- /dev/null +++ b/Microservice.Logic/Orders/Events/OrderPlacedEvent.cs @@ -0,0 +1,10 @@ +namespace Microservice.Logic.Orders.Events +{ + public class OrderPlacedEvent + { + public long Id { get; set; } + public string Name { get; set; } + public int Quantity { get; set; } + public int QuantityBeforeReduction { get; set; } + } +} diff --git a/Microservice.Logic/Orders/Events/OrderPlacedSubscriptionEvent.cs b/Microservice.Logic/Orders/Events/OrderPlacedSubscriptionEvent.cs new file mode 100644 index 0000000..7b7d605 --- /dev/null +++ b/Microservice.Logic/Orders/Events/OrderPlacedSubscriptionEvent.cs @@ -0,0 +1,9 @@ +namespace Microservice.Logic.Orders.Events +{ + public class OrderPlacedSubscriptionEvent + { + public long Id { get; set; } + public long PersonId { get; set; } + public int Quantity { get; set; } + } +} diff --git a/Microservice.Logic/Orders/Events/OrderUpdatedEvent.cs b/Microservice.Logic/Orders/Events/OrderUpdatedEvent.cs new file mode 100644 index 0000000..03b2da1 --- /dev/null +++ b/Microservice.Logic/Orders/Events/OrderUpdatedEvent.cs @@ -0,0 +1,9 @@ +namespace Microservice.Logic.Orders.Events +{ + public class OrderUpdatedEvent + { + public long Id { get; set; } + public string Name { get; set; } + public int Quantity { get; set; } + } +} diff --git a/Microservice.Logic/Orders/Handlers/CreateOrderHandler.cs b/Microservice.Logic/Orders/Handlers/CreateOrderHandler.cs index 3d2d054..bdabea1 100644 --- a/Microservice.Logic/Orders/Handlers/CreateOrderHandler.cs +++ b/Microservice.Logic/Orders/Handlers/CreateOrderHandler.cs @@ -1,34 +1,35 @@ using MediatR; using Microservice.Db; -using Microservice.Db.EntityModels; using Microservice.Logic.Orders.Commands; +using Microservice.Logic.Orders.EventPublishers; using Microservice.Logic.Orders.Responses; using System.Threading; using System.Threading.Tasks; namespace Microservice.Logic.Orders.Handlers { - public class CreateOrderHandler: IRequestHandler + public class CreateOrderHandler : IRequestHandler { private readonly MicroserviceDbContext _dbContext; + private readonly IOrderCreatedEventPublisher _createdEventPublisher; - public CreateOrderHandler(MicroserviceDbContext dbContext) + public CreateOrderHandler(MicroserviceDbContext dbContext, IOrderCreatedEventPublisher createdEventPublisher) { _dbContext = dbContext; + _createdEventPublisher = createdEventPublisher; } public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken) { - var entity = new Order - { - Name = request.Name - }; + var entity = request.ToCreateEntity(); _dbContext.Orders.Add(entity); await _dbContext.SaveChangesAsync(cancellationToken); var response = entity.ToResponse(); + await _createdEventPublisher.Publish(entity.ToCreatedEvent()); + return response; } } diff --git a/Microservice.Logic/Orders/Handlers/PatchOrderHandler.cs b/Microservice.Logic/Orders/Handlers/PatchOrderHandler.cs index d8d9dd2..190d9e6 100644 --- a/Microservice.Logic/Orders/Handlers/PatchOrderHandler.cs +++ b/Microservice.Logic/Orders/Handlers/PatchOrderHandler.cs @@ -2,6 +2,7 @@ using Microservice.Db; using Microservice.Db.EntityModels; using Microservice.Logic.Orders.Commands; +using Microservice.Logic.Orders.EventPublishers; using Microservice.Logic.Orders.Responses; using Microsoft.EntityFrameworkCore; using System.Diagnostics; @@ -10,13 +11,15 @@ namespace Microservice.Logic.Orders.Handlers { - public class PatchOrderHandler : IRequestHandler + public class PatchOrderHandler : IRequestHandler { private readonly MicroserviceDbContext _dbContext; + private readonly IOrderPatchedEventPublisher _updatedEventPublisher; - public PatchOrderHandler(MicroserviceDbContext dbContext) + public PatchOrderHandler(MicroserviceDbContext dbContext, IOrderPatchedEventPublisher updatedEventPublisher) { _dbContext = dbContext; + _updatedEventPublisher = updatedEventPublisher; } public async Task Handle(PatchOrderCommand command, CancellationToken cancellationToken) @@ -47,11 +50,13 @@ await _dbContext.Orders { Id = originalEntity.Id, Name = model.Name - }; + }; _dbContext.Update(updatedEntity); await _dbContext.SaveChangesAsync(cancellationToken); + await _updatedEventPublisher.Publish(updatedEntity.ToUpdatedEvent()); + var response = updatedEntity.ToResponse(); return response; } diff --git a/Microservice.Logic/Orders/Handlers/PutOrderPlacedHandler.cs b/Microservice.Logic/Orders/Handlers/PutOrderPlacedHandler.cs new file mode 100644 index 0000000..7fdbc71 --- /dev/null +++ b/Microservice.Logic/Orders/Handlers/PutOrderPlacedHandler.cs @@ -0,0 +1,46 @@ +using MediatR; +using Microservice.Db; +using Microservice.Logic.Orders.Commands; +using Microservice.Logic.Orders.EventPublishers; +using Microservice.Logic.Orders.Responses; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microservice.Logic.Orders.Handlers +{ + public class PutOrderPlacedHandler : IRequestHandler + { + private readonly MicroserviceDbContext _dbContext; + private readonly IOrderPlacedEventPublisher _orderPlacedEventPublisher; + + public PutOrderPlacedHandler(MicroserviceDbContext dbContext, IOrderPlacedEventPublisher orderPlacedEventPublisher) + { + _dbContext = dbContext; + _orderPlacedEventPublisher = orderPlacedEventPublisher; + } + + public async Task Handle(PutOrderPlacedCommand request, CancellationToken cancellationToken) + { + var order = await _dbContext.Orders.FindAsync(request.Id); + + if (order == null) + throw new KeyNotFoundException($"Unable to modify order because an entry with Id: {request.Id} could not be found"); + + if (order.Quantity < request.Quantity) + throw new ArgumentOutOfRangeException($"Unable to place order as the requested quantity ({request.Quantity}) is greater than the in stock quantity ({order.Quantity})"); + + var quantityBeforeReduction = order.Quantity; + order.Quantity -= request.Quantity; + + await _dbContext.SaveChangesAsync(cancellationToken); + + await _orderPlacedEventPublisher.Publish(order.ToOrderPlacedEvent(quantityBeforeReduction)); + + var response = order.ToResponse(); + + return response; + } + } +} diff --git a/Microservice.Logic/Orders/Integration/IOrderCreatedEventPublisher.cs b/Microservice.Logic/Orders/Integration/IOrderCreatedEventPublisher.cs deleted file mode 100644 index 888c032..0000000 --- a/Microservice.Logic/Orders/Integration/IOrderCreatedEventPublisher.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Microservice.Logic.Orders.Integration -{ - public interface IOrderCreatedEventPublisher - { - } -} diff --git a/Microservice.Logic/Orders/Integration/OrderCreatedEventPublisher.cs b/Microservice.Logic/Orders/Integration/OrderCreatedEventPublisher.cs deleted file mode 100644 index 4572eb9..0000000 --- a/Microservice.Logic/Orders/Integration/OrderCreatedEventPublisher.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microservice.Logic.Orders.Events; -using System.Threading.Tasks; -using Microservice.RabbitMessageBroker; - -namespace Microservice.Logic.Orders.Integration -{ - public class OrderCreatedEventPublisher : IOrderCreatedEventPublisher - { - private IRabbitMessageBrokerClient BrokerClient { get; } - - public OrderCreatedEventPublisher(IRabbitMessageBrokerClient brokerClient) - { - BrokerClient = brokerClient; - } - - public async Task Publish(OrderCreatedEvent createdEvent) - { - await BrokerClient.Publish("OrderCreated", createdEvent); - - } - } -} diff --git a/Microservice.Logic/Orders/OrderMapper.cs b/Microservice.Logic/Orders/OrderMapper.cs index f3c8fe7..58a0833 100644 --- a/Microservice.Logic/Orders/OrderMapper.cs +++ b/Microservice.Logic/Orders/OrderMapper.cs @@ -1,4 +1,6 @@ using Microservice.Db.EntityModels; +using Microservice.Logic.Orders.Commands; +using Microservice.Logic.Orders.Events; using Microservice.Logic.Orders.Models; using Microservice.Logic.Orders.Responses; @@ -6,12 +8,23 @@ namespace Microservice.Logic.Orders { public static class OrderMapper { + public static Order ToCreateEntity(this CreateOrderCommand command) + { + var result = new Order + { + Name = command.Name, + Quantity = command.Quantity + }; + + return result; + } public static OrderResponse ToResponse(this Order order) { var result = new OrderResponse { Id = order.Id, - Name = order.Name + Name = order.Name, + Quantity = order.Quantity }; return result; @@ -26,5 +39,59 @@ public static OrderPatchModel ToPatchModal(this Order order) return result; } + + public static PutOrderPlacedCommand ToPutPlacedOrderCommand( + this OrderPlacedSubscriptionEvent subscriptionEvent) + { + var result = new PutOrderPlacedCommand + ( + subscriptionEvent.Id, + subscriptionEvent.PersonId, + subscriptionEvent.Quantity + ); + + return result; + } + + public static OrderUpdatedEvent ToUpdatedEvent( + this Order order) + { + var result = new OrderUpdatedEvent + { + Id = order.Id, + Name = order.Name, + Quantity = order.Quantity, + }; + + return result; + } + + public static OrderPlacedEvent ToOrderPlacedEvent( + this Order order, + int quantityBeforeReduction) + { + var result = new OrderPlacedEvent + { + Id = order.Id, + Name = order.Name, + Quantity = order.Quantity, + QuantityBeforeReduction = quantityBeforeReduction + }; + + return result; + } + + public static OrderCreatedEvent ToCreatedEvent( + this Order order) + { + var result = new OrderCreatedEvent + { + Id = order.Id, + Name = order.Name, + Quantity = order.Quantity + }; + + return result; + } } } diff --git a/Microservice.Logic/Orders/Responses/OrderResponse.cs b/Microservice.Logic/Orders/Responses/OrderResponse.cs index 3180d64..c2ba918 100644 --- a/Microservice.Logic/Orders/Responses/OrderResponse.cs +++ b/Microservice.Logic/Orders/Responses/OrderResponse.cs @@ -4,5 +4,6 @@ public class OrderResponse { public long Id { get; set; } public string Name { get; set; } + public int Quantity { get; set; } } } diff --git a/Microservice.MessageBus/IMessageBusPublisher.cs b/Microservice.MessageBus/IMessageBusPublisher.cs new file mode 100644 index 0000000..a68e845 --- /dev/null +++ b/Microservice.MessageBus/IMessageBusPublisher.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Microservice.MessageBus +{ + public interface IMessageBusPublisher + { + Task Publish(TMessage message); + } +} diff --git a/Microservice.MessageBus/Microservice.MessageBus.csproj b/Microservice.MessageBus/Microservice.MessageBus.csproj new file mode 100644 index 0000000..40a6b91 --- /dev/null +++ b/Microservice.MessageBus/Microservice.MessageBus.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + + diff --git a/Microservice.MessageBus/Orders/EventPublishers/OrderCreatedEventPublisher.cs b/Microservice.MessageBus/Orders/EventPublishers/OrderCreatedEventPublisher.cs new file mode 100644 index 0000000..8616fd2 --- /dev/null +++ b/Microservice.MessageBus/Orders/EventPublishers/OrderCreatedEventPublisher.cs @@ -0,0 +1,18 @@ +using Microservice.RabbitMessageBroker; +using System.Threading.Tasks; + +namespace Microservice.MessageBus.Orders.EventPublishers +{ + public class OrderCreatedEventPublisher : IMessageBusPublisher + { + private IRabbitMessageBrokerClient BrokerClient { get; } + + public OrderCreatedEventPublisher(IRabbitMessageBrokerClient brokerClient) + { + BrokerClient = brokerClient; + } + + public async Task Publish(TMessage createdEvent) + => await BrokerClient.Publish("OrderCreated", createdEvent); + } +} diff --git a/Microservice.MessageBus/Orders/EventPublishers/OrderUpdatedEventPublisher.cs b/Microservice.MessageBus/Orders/EventPublishers/OrderUpdatedEventPublisher.cs new file mode 100644 index 0000000..554b979 --- /dev/null +++ b/Microservice.MessageBus/Orders/EventPublishers/OrderUpdatedEventPublisher.cs @@ -0,0 +1,18 @@ +using Microservice.RabbitMessageBroker; +using System.Threading.Tasks; + +namespace Microservice.MessageBus.Orders.EventPublishers +{ + public class OrderUpdatedEventPublisher : IMessageBusPublisher + { + private IRabbitMessageBrokerClient BrokerClient { get; } + + public OrderUpdatedEventPublisher(IRabbitMessageBrokerClient brokerClient) + { + BrokerClient = brokerClient; + } + + public async Task Publish(TMessage message) + => await BrokerClient.Publish("OrderUpdated", message); + } +} diff --git a/Microservice.MessageBus/Orders/Events/OrderCreatedEvent.cs b/Microservice.MessageBus/Orders/Events/OrderCreatedEvent.cs new file mode 100644 index 0000000..45bfb14 --- /dev/null +++ b/Microservice.MessageBus/Orders/Events/OrderCreatedEvent.cs @@ -0,0 +1,8 @@ +namespace Microservice.MessageBus.Orders.Events +{ + public class OrderCreatedEvent + { + public long Id { get; set; } + public string Name { get; set; } + } +} diff --git a/Microservice.Api/MessageBus/OrderSubscriptions/Events/OrderPlacedSubscriptionEvent.cs b/Microservice.MessageBus/Orders/Events/OrderPlacedSubscriptionEvent.cs similarity index 60% rename from Microservice.Api/MessageBus/OrderSubscriptions/Events/OrderPlacedSubscriptionEvent.cs rename to Microservice.MessageBus/Orders/Events/OrderPlacedSubscriptionEvent.cs index 2670e25..711d147 100644 --- a/Microservice.Api/MessageBus/OrderSubscriptions/Events/OrderPlacedSubscriptionEvent.cs +++ b/Microservice.MessageBus/Orders/Events/OrderPlacedSubscriptionEvent.cs @@ -1,4 +1,4 @@ -namespace Microservice.Api.MessageBus.Orders.IntegrationEvents +namespace Microservice.Logic.Orders.Messagebus.Events { public class OrderPlacedSubscriptionEvent { diff --git a/Microservice.MessageBus/Orders/Events/OrderUpdatedEvent.cs b/Microservice.MessageBus/Orders/Events/OrderUpdatedEvent.cs new file mode 100644 index 0000000..b7f8cf3 --- /dev/null +++ b/Microservice.MessageBus/Orders/Events/OrderUpdatedEvent.cs @@ -0,0 +1,8 @@ +namespace Microservice.MessageBus.Orders.Events +{ + public class OrderUpdatedEvent + { + public long Id { get; set; } + public string Name { get; set; } + } +} diff --git a/Microservice.MessageBus/Orders/SubscriptionHandlers/PlacedOrderSubscriptionHandler.cs b/Microservice.MessageBus/Orders/SubscriptionHandlers/PlacedOrderSubscriptionHandler.cs new file mode 100644 index 0000000..c164f6c --- /dev/null +++ b/Microservice.MessageBus/Orders/SubscriptionHandlers/PlacedOrderSubscriptionHandler.cs @@ -0,0 +1,27 @@ +using MediatR; +using Microservice.MessageBus.Orders.Events; +using Microservice.RabbitMessageBrokerHelpers.Handlers; +using System.Threading; +using System.Threading.Tasks; + +namespace Microservice.MessageBus.Orders.SubscriptionHandlers +{ + public class PlacedOrderSubscriptionHandler : MessageBrokerSubscriptionHandler + { + private readonly IMediator _mediator; + public PlacedOrderSubscriptionHandler(IMediator mediator) + { + _mediator = mediator; + } + + public override async Task Handle(OrderPlacedSubscriptionEvent eventModel, CancellationToken cancellationToken) + { + var order = await _mediator.Send(new GetOrderByIdQuery(eventModel.Id), cancellationToken); + + if (order == null) + return; + + // Do some custom operation here + } + } +} diff --git a/Microservice.RabbitMessageBroker.Integration.Tests/Microservice.RabbitMessageBroker.Integration.Tests.csproj b/Microservice.RabbitMessageBroker.Integration.Tests/Microservice.RabbitMessageBroker.Integration.Tests.csproj index 4f12b18..3b0101c 100644 --- a/Microservice.RabbitMessageBroker.Integration.Tests/Microservice.RabbitMessageBroker.Integration.Tests.csproj +++ b/Microservice.RabbitMessageBroker.Integration.Tests/Microservice.RabbitMessageBroker.Integration.Tests.csproj @@ -20,14 +20,14 @@ - + + - - - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/Microservice.RabbitMessageBroker.Integration.Tests/RabbitMessageBusTests.cs b/Microservice.RabbitMessageBroker.Integration.Tests/RabbitMessageBusTests.cs index 306fe7b..bf62e9f 100644 --- a/Microservice.RabbitMessageBroker.Integration.Tests/RabbitMessageBusTests.cs +++ b/Microservice.RabbitMessageBroker.Integration.Tests/RabbitMessageBusTests.cs @@ -1,4 +1,5 @@ -using Microservice.RabbitMessageBroker.Configuration; +using Microservice.RabbitMQMessageBrokerExtension; +using Microservice.RabbitMQMessageBrokerExtension.Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; @@ -23,8 +24,9 @@ public void SetUp() var services = new ServiceCollection(); services - .AddMessageBroker(config.GetSection("RabbitMessageBrokerSettings")) - .AddLogging(); + .AddRabbitMqMessageBroker(config.GetSection("RabbitMessageBrokerSettings")) + .AddLogging() + ; MessageBrokerClient = services.BuildServiceProvider().GetService(); } diff --git a/Microservice.RabbitMessageBroker/Configuration/MessageBrokerServiceCollectionExtensions.cs b/Microservice.RabbitMessageBroker/Configuration/MessageBrokerServiceCollectionExtensions.cs index 6d8d627..194d0b9 100644 --- a/Microservice.RabbitMessageBroker/Configuration/MessageBrokerServiceCollectionExtensions.cs +++ b/Microservice.RabbitMessageBroker/Configuration/MessageBrokerServiceCollectionExtensions.cs @@ -5,7 +5,7 @@ namespace Microservice.RabbitMessageBroker.Configuration { public static class MessageBrokerServiceCollectionExtensions { - public static IServiceCollection AddMessageBroker(this IServiceCollection container, IConfigurationSection configuration) + public static IServiceCollection AddRabbitMqMessageBroker(this IServiceCollection container, IConfigurationSection configuration) { container .Configure(configuration) diff --git a/Microservice.RabbitMessageBroker/IRabbitMessageBrokerClient.cs b/Microservice.RabbitMessageBroker/IRabbitMessageBrokerClient.cs index 8fce1e1..f0c7133 100644 --- a/Microservice.RabbitMessageBroker/IRabbitMessageBrokerClient.cs +++ b/Microservice.RabbitMessageBroker/IRabbitMessageBrokerClient.cs @@ -7,17 +7,19 @@ namespace Microservice.RabbitMessageBroker public interface IRabbitMessageBrokerClient { Task Subscribe( - string topic, - string subscriptionId, + string topic, + string subscriptionId, Func onReceive, - Func configure = null); + Func configure = null + ); Task Subscribe( string topic, string subscriptionId, Func onReceive, Type eventModelType, - Func config = null); + Func configure = null + ); Task Publish(string topic, T message); diff --git a/Microservice.RabbitMessageBroker/RabbitMessageBrokerClient.cs b/Microservice.RabbitMessageBroker/RabbitMessageBrokerClient.cs index afd7b8b..20dd745 100644 --- a/Microservice.RabbitMessageBroker/RabbitMessageBrokerClient.cs +++ b/Microservice.RabbitMessageBroker/RabbitMessageBrokerClient.cs @@ -18,7 +18,7 @@ namespace Microservice.RabbitMessageBroker { public class RabbitMessageBrokerClient : IRabbitMessageBrokerClient { - private IModel _channel { get; set; } + private IModel Channel { get; set; } private readonly ILogger _logger; private readonly IOptions _options; @@ -76,10 +76,10 @@ protected async Task GetChannel(string topic, string subscriptionId = nu if (_options.Value == null) throw new Exception("RabbitMQ: Message broker client credentials not configured {@details}"); - if (_channel == null || _channel.IsClosed) - _channel = await TryConnect(topic, subscriptionId); + if (Channel == null || Channel.IsClosed) + Channel = await TryConnect(topic, subscriptionId); - return _channel; + return Channel; } public Task Subscribe( @@ -197,6 +197,8 @@ public async Task Subscribe(string topic, }; } + + public async Task Publish(string topic, T message) { var tcs = new TaskCompletionSource(); diff --git a/Microservice.RabbitMessageBrokerBackgroundJobHelpers/Configuration/MessageBrokerBackgroundJobHelpersServiceCollectionExtension.cs b/Microservice.RabbitMessageBrokerBackgroundJobHelpers/Configuration/MessageBrokerBackgroundJobHelpersServiceCollectionExtension.cs new file mode 100644 index 0000000..10e11f1 --- /dev/null +++ b/Microservice.RabbitMessageBrokerBackgroundJobHelpers/Configuration/MessageBrokerBackgroundJobHelpersServiceCollectionExtension.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microservice.RabbitMessageBrokerBackgroundJobHelpers.Configuration +{ + class MessageBrokerBackgroundJobHelpersServiceCollectionExtension + { + } +} diff --git a/Microservice.RabbitMessageBrokerBackgroundJobHelpers/Microservice.RabbitMessageBrokerBackgroundJobHelpers.csproj b/Microservice.RabbitMessageBrokerBackgroundJobHelpers/Microservice.RabbitMessageBrokerBackgroundJobHelpers.csproj new file mode 100644 index 0000000..dbf9da1 --- /dev/null +++ b/Microservice.RabbitMessageBrokerBackgroundJobHelpers/Microservice.RabbitMessageBrokerBackgroundJobHelpers.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + diff --git a/Microservice.RabbitMessageBrokerHelpers/BackgroundJobs/MessageBrokerSubscriptionBackgroundJob.cs b/Microservice.RabbitMessageBrokerHelpers/BackgroundJobs/MessageBrokerSubscriptionBackgroundJob.cs new file mode 100644 index 0000000..968db6f --- /dev/null +++ b/Microservice.RabbitMessageBrokerHelpers/BackgroundJobs/MessageBrokerSubscriptionBackgroundJob.cs @@ -0,0 +1,85 @@ +using Microservice.HangfireBackgroundJobServer.Infrastructure; +using Microservice.RabbitMessageBrokerHelpers.Builders; +using Microservice.RabbitMessageBrokerHelpers.Handlers; +using Microservice.RabbitMessageBrokerHelpers.Models; +using Microservice.RabbitMQMessageBrokerExtension; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microservice.RabbitMessageBrokerHelpers.BackgroundJobs +{ + public class MessageBrokerSubscriptionBackgroundJob : IHostedService + { + private readonly IServiceProvider _serviceProvider; + private readonly IBackgroundProcessingClient _backgroundProcessingClient; + private readonly IRabbitMessageBrokerClient _messageBrokerClient; + private readonly ILogger _logger; + private readonly MessageBrokerSubscriptionsConfigurationBuilder _configurationBuilder; + private List _unsubscribeCallbacks; + + public MessageBrokerSubscriptionBackgroundJob( + MessageBrokerSubscriptionsConfigurationBuilder configurationBuilder, + IServiceProvider serviceProvider, + IRabbitMessageBrokerClient messageBrokerClient, + ILogger logger, + IBackgroundProcessingClient backgroundProcessingClient) + { + _configurationBuilder = configurationBuilder; + _serviceProvider = serviceProvider; + _messageBrokerClient = messageBrokerClient; + _logger = logger; + _backgroundProcessingClient = backgroundProcessingClient; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + var tasks = _configurationBuilder.Subscriptions + .Select(subscription => _messageBrokerClient.Subscribe( + subscription.Topic, + subscription.Pool, + eventModel => Handle(eventModel, subscription), + subscription.EventModel)) + .ToList(); + + await Task.WhenAll(tasks); + + _unsubscribeCallbacks = tasks.Select(t => t.Result).ToList(); + } + + public async Task Handle(object eventModel, MessageBrokerSubscription subscription) + { + using (var scope = _serviceProvider.CreateScope()) + { + var handler = scope.ServiceProvider.GetService(subscription.Handler) as IMessageBrokerSubscriptionHandler; + if (handler == null) + { + _logger.LogError("Could not resolve subscription handler. {@details}", new + { + CouldNotResolveIntegrationEventHandler = new + { + subscription + } + }); + } + + await _backgroundProcessingClient.Run(() => handler.HandleEventModel(eventModel)); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + foreach (var unsubscribeCallback in _unsubscribeCallbacks) + { + unsubscribeCallback(); + } + + return Task.CompletedTask; + } + } +} diff --git a/Microservice.RabbitMessageBrokerHelpers/Builders/MessageBrokerSubscriptionsConfigurationBuilder.cs b/Microservice.RabbitMessageBrokerHelpers/Builders/MessageBrokerSubscriptionsConfigurationBuilder.cs new file mode 100644 index 0000000..3eda3e7 --- /dev/null +++ b/Microservice.RabbitMessageBrokerHelpers/Builders/MessageBrokerSubscriptionsConfigurationBuilder.cs @@ -0,0 +1,41 @@ +using Microservice.RabbitMessageBrokerHelpers.Handlers; +using Microservice.RabbitMessageBrokerHelpers.Models; +using System.Collections.Generic; + +namespace Microservice.RabbitMessageBrokerHelpers.Builders +{ + public class MessageBrokerSubscriptionsConfigurationBuilder + { + public List Subscriptions; + public string Pool; + + public MessageBrokerSubscriptionsConfigurationBuilder() + { + Subscriptions = new List(); + } + public MessageBrokerSubscriptionsConfigurationBuilder UsePool(string pool) + { + Pool = pool; + return this; + } + + public MessageBrokerSubscriptionsConfigurationBuilder Subscribe(string topic) + where THandler : MessageBrokerSubscriptionHandler + where TEventModel : class + { + Subscriptions.Add(new MessageBrokerSubscription + { + Topic = topic, + Handler = typeof(THandler), + EventModel = typeof(TEventModel) + }); + return this; + } + + public MessageBrokerSubscriptionsConfigurationBuilder Subscribe(string topic) + where THandler : MessageBrokerSubscriptionHandler + { + return Subscribe(topic); + } + } +} diff --git a/Microservice.RabbitMessageBrokerHelpers/Configuration/ServiceCollectionExtensions.cs b/Microservice.RabbitMessageBrokerHelpers/Configuration/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..76cab93 --- /dev/null +++ b/Microservice.RabbitMessageBrokerHelpers/Configuration/ServiceCollectionExtensions.cs @@ -0,0 +1,40 @@ +using Microservice.RabbitMessageBrokerHelpers.BackgroundJobs; +using Microservice.RabbitMessageBrokerHelpers.Builders; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Microservice.RabbitMessageBrokerHelpers.Configuration +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddMessageBrokerSubscriptions(this IServiceCollection services, + Action configure) + { + var configurationBuilder = new MessageBrokerSubscriptionsConfigurationBuilder(); + configure.Invoke(configurationBuilder); + + foreach (var subscription in configurationBuilder.Subscriptions) + services.AddTransient(subscription.Handler); + + return services + .AddSingleton(configurationBuilder) + .AddHostedService() + ; + } + + public static IServiceCollection AddMessageBrokerPublishers(this IServiceCollection services, + Action configure) + { + var configurationBuilder = new MessageBrokerSubscriptionsConfigurationBuilder(); + configure.Invoke(configurationBuilder); + + foreach (var subscription in configurationBuilder.Subscriptions) + services.AddTransient(subscription.Handler); + + return services + .AddSingleton(configurationBuilder) + .AddHostedService() + ; + } + } +} diff --git a/Microservice.RabbitMessageBrokerHelpers/Handlers/EventPublisher.cs b/Microservice.RabbitMessageBrokerHelpers/Handlers/EventPublisher.cs new file mode 100644 index 0000000..da921ca --- /dev/null +++ b/Microservice.RabbitMessageBrokerHelpers/Handlers/EventPublisher.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Microservice.RabbitMessageBrokerHelpers.Handlers +{ + public abstract class EventPublisher where TPublishEvent : class + { + public abstract Task Publish(TPublishEvent eventModel); + } +} diff --git a/Microservice.RabbitMessageBrokerHelpers/Handlers/IMessageBrokerSubscriptionHandler .cs b/Microservice.RabbitMessageBrokerHelpers/Handlers/IMessageBrokerSubscriptionHandler .cs new file mode 100644 index 0000000..511674f --- /dev/null +++ b/Microservice.RabbitMessageBrokerHelpers/Handlers/IMessageBrokerSubscriptionHandler .cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Microservice.RabbitMessageBrokerHelpers.Handlers +{ + public interface IMessageBrokerSubscriptionHandler + { + Task HandleEventModel(object eventModel); + } +} diff --git a/Microservice.RabbitMessageBrokerHelpers/Handlers/MessageBrokerSubscriptionHandler.cs b/Microservice.RabbitMessageBrokerHelpers/Handlers/MessageBrokerSubscriptionHandler.cs new file mode 100644 index 0000000..a101e60 --- /dev/null +++ b/Microservice.RabbitMessageBrokerHelpers/Handlers/MessageBrokerSubscriptionHandler.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; + +namespace Microservice.RabbitMessageBrokerHelpers.Handlers +{ + public abstract class MessageBrokerSubscriptionHandler : IMessageBrokerSubscriptionHandler where TEventModel : class + { + public Task HandleEventModel(object eventModel) + { + return Handle(eventModel as TEventModel); + } + + public abstract Task Handle(TEventModel eventModel); + } +} diff --git a/Microservice.RabbitMessageBrokerHelpers/Microservice.RabbitMessageBrokerHelpers.csproj b/Microservice.RabbitMessageBrokerHelpers/Microservice.RabbitMessageBrokerHelpers.csproj new file mode 100644 index 0000000..523ef31 --- /dev/null +++ b/Microservice.RabbitMessageBrokerHelpers/Microservice.RabbitMessageBrokerHelpers.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + + + diff --git a/Microservice.RabbitMessageBrokerHelpers/Models/MessageBrokerSubscription.cs b/Microservice.RabbitMessageBrokerHelpers/Models/MessageBrokerSubscription.cs new file mode 100644 index 0000000..b2c9f17 --- /dev/null +++ b/Microservice.RabbitMessageBrokerHelpers/Models/MessageBrokerSubscription.cs @@ -0,0 +1,12 @@ +using System; + +namespace Microservice.RabbitMessageBrokerHelpers.Models +{ + public class MessageBrokerSubscription + { + public string Pool { get; set; } + public Type Handler { get; set; } + public Type EventModel { get; set; } + public string Topic { get; set; } + } +} diff --git a/Microservice.sln b/Microservice.sln index 2d755af..bbcffaa 100644 --- a/Microservice.sln +++ b/Microservice.sln @@ -13,10 +13,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microservice.Db", "Microser EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microservice.RabbitMessageBroker.Integration.Tests", "Microservice.RabbitMessageBroker.Integration.Tests\Microservice.RabbitMessageBroker.Integration.Tests.csproj", "{40BDA940-4DE3-45BD-B6B8-9761B9F5DA77}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microservice.RabbitMessageBroker", "Microservice.RabbitMessageBroker\Microservice.RabbitMessageBroker.csproj", "{C58B170B-B159-4549-8DE1-5DC229535139}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microservice.HangfireBackgroundJobServer", "Microservice.HangfireBackgroundJobServer\Microservice.HangfireBackgroundJobServer.csproj", "{96C85DBD-DE58-4D02-A01B-E10D521CB089}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microservice.RabbitMessageBrokerHelpers", "Microservice.RabbitMessageBrokerHelpers\Microservice.RabbitMessageBrokerHelpers.csproj", "{CB07E0EA-3F3A-43BE-A77D-4BC77DD24A80}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -43,14 +43,14 @@ Global {40BDA940-4DE3-45BD-B6B8-9761B9F5DA77}.Debug|Any CPU.Build.0 = Debug|Any CPU {40BDA940-4DE3-45BD-B6B8-9761B9F5DA77}.Release|Any CPU.ActiveCfg = Release|Any CPU {40BDA940-4DE3-45BD-B6B8-9761B9F5DA77}.Release|Any CPU.Build.0 = Release|Any CPU - {C58B170B-B159-4549-8DE1-5DC229535139}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C58B170B-B159-4549-8DE1-5DC229535139}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C58B170B-B159-4549-8DE1-5DC229535139}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C58B170B-B159-4549-8DE1-5DC229535139}.Release|Any CPU.Build.0 = Release|Any CPU {96C85DBD-DE58-4D02-A01B-E10D521CB089}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {96C85DBD-DE58-4D02-A01B-E10D521CB089}.Debug|Any CPU.Build.0 = Debug|Any CPU {96C85DBD-DE58-4D02-A01B-E10D521CB089}.Release|Any CPU.ActiveCfg = Release|Any CPU {96C85DBD-DE58-4D02-A01B-E10D521CB089}.Release|Any CPU.Build.0 = Release|Any CPU + {CB07E0EA-3F3A-43BE-A77D-4BC77DD24A80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB07E0EA-3F3A-43BE-A77D-4BC77DD24A80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB07E0EA-3F3A-43BE-A77D-4BC77DD24A80}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB07E0EA-3F3A-43BE-A77D-4BC77DD24A80}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE