From d9c004a92da98da2fdd25cc246235987f7d3ce59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Fri, 31 Mar 2017 10:30:56 +0200 Subject: [PATCH] Added integration event for cleaning basket when order is created --- .../OrderStartedIntegrationEventHandler.cs | 27 ++++++++++++ .../Events/OrderStartedIntegrationEvent.cs | 19 ++++++++ src/Services/Basket/Basket.API/Startup.cs | 5 +++ ...PaymentMethodVerifiedDomainEventHandler.cs | 44 ++++++++++++++++--- .../AutofacModules/MediatorModule.cs | 3 +- ...131634_IntegrationEventInitial.Designer.cs | 43 ++++++++++++++++++ .../20170330131634_IntegrationEventInitial.cs | 34 ++++++++++++++ ...IntegrationEventLogContextModelSnapshot.cs | 42 ++++++++++++++++++ .../Events/OrderStartedIntegrationEvent.cs | 19 ++++++++ .../Ordering/Ordering.API/Ordering.API.csproj | 16 +++++-- src/Services/Ordering/Ordering.API/Startup.cs | 17 ++++++- .../Ordering.Infrastructure.csproj | 5 ++- src/Web/WebMVC/Controllers/OrderController.cs | 3 -- 13 files changed, 261 insertions(+), 16 deletions(-) create mode 100644 src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs create mode 100644 src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs create mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.Designer.cs create mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.cs create mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextModelSnapshot.cs create mode 100644 src/Services/Ordering/Ordering.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs new file mode 100644 index 000000000..e35badc64 --- /dev/null +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs @@ -0,0 +1,27 @@ +using Basket.API.IntegrationEvents.Events; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Basket.API.IntegrationEvents.EventHandling +{ + public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler + { + private readonly IBasketRepository _repository; + public OrderStartedIntegrationEventHandler(IBasketRepository repository) + { + _repository = repository; + } + + public async Task Handle(OrderStartedIntegrationEvent @event) + { + await _repository.DeleteBasketAsync(@event.UserId.ToString()); + } + } +} + + + diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs b/src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs new file mode 100644 index 000000000..3b5726e83 --- /dev/null +++ b/src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs @@ -0,0 +1,19 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Basket.API.IntegrationEvents.Events +{ + // Integration Events notes: + // An Event is “something that has happened in the past”, therefore its name has to be + // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. + public class OrderStartedIntegrationEvent : IntegrationEvent + { + public string UserId { get; } + + public OrderStartedIntegrationEvent(string userId) => + UserId = userId; + } +} diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index f783fccff..7d16c5691 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -17,6 +17,8 @@ using System; using Microsoft.Extensions.HealthChecks; using System.Threading.Tasks; using Basket.API.Infrastructure.Filters; +using Basket.API.IntegrationEvents.Events; +using Basket.API.IntegrationEvents.EventHandling; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -91,6 +93,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API services.AddTransient(); services.AddTransient, ProductPriceChangedIntegrationEventHandler>(); + services.AddTransient, OrderStartedIntegrationEventHandler>(); var serviceProvider = services.BuildServiceProvider(); var configuration = serviceProvider.GetRequiredService>().Value; @@ -117,8 +120,10 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API .UseSwaggerUi(); var catalogPriceHandler = app.ApplicationServices.GetService>(); + var orderStartedHandler = app.ApplicationServices.GetService>(); var eventBus = app.ApplicationServices.GetRequiredService(); eventBus.Subscribe(catalogPriceHandler); + eventBus.Subscribe(orderStartedHandler); } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs index 1ac863b1e..550d7055e 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/BuyerAndPaymentMethodVerified/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs @@ -1,8 +1,15 @@ using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; using Microsoft.Extensions.Logging; +using Ordering.API.IntegrationEvents.Events; using Ordering.Domain.Events; using System; +using System.Data.Common; using System.Threading.Tasks; namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVerified @@ -12,9 +19,16 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri { private readonly IOrderRepository _orderRepository; private readonly ILoggerFactory _logger; - public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(IOrderRepository orderRepository, ILoggerFactory logger) + private readonly Func _integrationEventLogServiceFactory; + private readonly IEventBus _eventBus; + + public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler( + IOrderRepository orderRepository, ILoggerFactory logger, IEventBus eventBus, + Func integrationEventLogServiceFactory) { _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); + _integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory)); + _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -26,12 +40,30 @@ namespace Ordering.API.Application.DomainEventHandlers.BuyerAndPaymentMethodVeri var orderToUpdate = await _orderRepository.GetAsync(buyerPaymentMethodVerifiedEvent.OrderId); orderToUpdate.SetBuyerId(buyerPaymentMethodVerifiedEvent.Buyer.Id); orderToUpdate.SetPaymentId(buyerPaymentMethodVerifiedEvent.Payment.Id); - - await _orderRepository.UnitOfWork - .SaveEntitiesAsync(); - + + var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(buyerPaymentMethodVerifiedEvent.Buyer.IdentityGuid); + //Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): + //See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency + var orderingContext = _orderRepository.UnitOfWork as OrderingContext; + var strategy = orderingContext.Database.CreateExecutionStrategy(); + + var eventLogService = _integrationEventLogServiceFactory(orderingContext.Database.GetDbConnection()); + await strategy.ExecuteAsync(async () => + { + // Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction + using (var transaction = orderingContext.Database.BeginTransaction()) + { + await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + await eventLogService.SaveEventAsync(orderStartedIntegrationEvent, orderingContext.Database.CurrentTransaction.GetDbTransaction()); + transaction.Commit(); + } + }); + _logger.CreateLogger(nameof(UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler)) .LogTrace($"Order with Id: {buyerPaymentMethodVerifiedEvent.OrderId} has been successfully updated with a payment method id: { buyerPaymentMethodVerifiedEvent.Payment.Id }"); + + _eventBus.Publish(orderStartedIntegrationEvent); + await eventLogService.MarkEventAsPublishedAsync(orderStartedIntegrationEvent); } - } + } } diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs index e741644f3..4f38dd59e 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs @@ -31,7 +31,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof builder.RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly) .As(o => o.GetInterfaces() .Where(i => i.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>))) - .Select(i => new KeyedService("IAsyncNotificationHandler", i))); + .Select(i => new KeyedService("IAsyncNotificationHandler", i))) + .AsImplementedInterfaces(); builder .RegisterAssemblyTypes(typeof(CreateOrderCommandValidator).GetTypeInfo().Assembly) diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.Designer.cs b/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.Designer.cs new file mode 100644 index 000000000..65b6a6e05 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.Designer.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; + +namespace Ordering.API.Infrastructure.IntegrationEventMigrations +{ + [DbContext(typeof(IntegrationEventLogContext))] + [Migration("20170330131634_IntegrationEventInitial")] + partial class IntegrationEventInitial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.IntegrationEventLogEntry", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd(); + + b.Property("Content") + .IsRequired(); + + b.Property("CreationTime"); + + b.Property("EventTypeName") + .IsRequired(); + + b.Property("State"); + + b.Property("TimesSent"); + + b.HasKey("EventId"); + + b.ToTable("IntegrationEventLog"); + }); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.cs b/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.cs new file mode 100644 index 000000000..9830ebf7b --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/20170330131634_IntegrationEventInitial.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ordering.API.Infrastructure.IntegrationEventMigrations +{ + public partial class IntegrationEventInitial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "IntegrationEventLog", + columns: table => new + { + EventId = table.Column(nullable: false), + Content = table.Column(nullable: false), + CreationTime = table.Column(nullable: false), + EventTypeName = table.Column(nullable: false), + State = table.Column(nullable: false), + TimesSent = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IntegrationEventLog", x => x.EventId); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "IntegrationEventLog"); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextModelSnapshot.cs b/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextModelSnapshot.cs new file mode 100644 index 000000000..29bd24729 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Infrastructure/IntegrationEventMigrations/IntegrationEventLogContextModelSnapshot.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; + +namespace Ordering.API.Infrastructure.IntegrationEventMigrations +{ + [DbContext(typeof(IntegrationEventLogContext))] + partial class IntegrationEventLogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.IntegrationEventLogEntry", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd(); + + b.Property("Content") + .IsRequired(); + + b.Property("CreationTime"); + + b.Property("EventTypeName") + .IsRequired(); + + b.Property("State"); + + b.Property("TimesSent"); + + b.HasKey("EventId"); + + b.ToTable("IntegrationEventLog"); + }); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs b/src/Services/Ordering/Ordering.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs new file mode 100644 index 000000000..8184483e5 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs @@ -0,0 +1,19 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.IntegrationEvents.Events +{ + // Integration Events notes: + // An Event is “something that has happened in the past”, therefore its name has to be + // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. + public class OrderStartedIntegrationEvent : IntegrationEvent + { + public string UserId { get; } + + public OrderStartedIntegrationEvent(string userId) => + UserId = userId; + } +} diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj index 5d9f6b258..6eed3a13c 100644 --- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj +++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj @@ -23,6 +23,9 @@ + + + @@ -38,16 +41,18 @@ + + - + - - + + @@ -73,4 +78,9 @@ + + + + + diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 2875eb2de..b7d90fa81 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -12,12 +12,17 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; + using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; + using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.Logging; using Ordering.Infrastructure; using System; + using System.Data.Common; using System.Reflection; public class Startup @@ -55,7 +60,7 @@ { checks.AddSqlCheck("Ordering_Db", Configuration["ConnectionString"]); }); - + services.AddEntityFrameworkSqlServer() .AddDbContext(options => { @@ -95,7 +100,11 @@ // Add application services. services.AddSingleton(); services.AddTransient(); + services.AddTransient>( + sp => (DbConnection c) => new IntegrationEventLogService(c)); + var serviceProvider = services.BuildServiceProvider(); + services.AddSingleton(new EventBusRabbitMQ(Configuration["EventBusConnection"])); services.AddOptions(); //configure autofac @@ -126,6 +135,12 @@ .UseSwaggerUi(); OrderingContextSeed.SeedAsync(app).Wait(); + + var integrationEventLogContext = new IntegrationEventLogContext( + new DbContextOptionsBuilder() + .UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Ordering.API")) + .Options); + integrationEventLogContext.Database.Migrate(); } protected virtual void ConfigureAuth(IApplicationBuilder app) diff --git a/src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj b/src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj index 4ec458dbd..bb756e552 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj +++ b/src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj @@ -16,8 +16,9 @@ - - + + + diff --git a/src/Web/WebMVC/Controllers/OrderController.cs b/src/Web/WebMVC/Controllers/OrderController.cs index 104ae9f9b..2755282a3 100644 --- a/src/Web/WebMVC/Controllers/OrderController.cs +++ b/src/Web/WebMVC/Controllers/OrderController.cs @@ -45,9 +45,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers var user = _appUserParser.Parse(HttpContext.User); await _orderSvc.CreateOrder(model); - //Empty basket for current user. - await _basketSvc.CleanBasket(user); - //Redirect to historic list. return RedirectToAction("Index"); }