From db35a5c3695044abd87ecc8517f2dbe28f4f37d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Wed, 3 Oct 2018 17:54:15 +0200 Subject: [PATCH 01/12] Initial approach --- .../Behaviors/TransactionBehaviour.cs | 55 +++++++++++++++++++ .../OrderingIntegrationEventService.cs | 19 +++---- .../AutofacModules/MediatorModule.cs | 2 + .../OrderingContext.cs | 48 +++++++++++++++- 4 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs diff --git a/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs new file mode 100644 index 000000000..d728d83cf --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs @@ -0,0 +1,55 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Behaviors +{ + public class TransactionBehaviour : IPipelineBehavior + { + private readonly ILogger> _logger; + private readonly OrderingContext _dbContext; + + public TransactionBehaviour(OrderingContext dbContext, ILogger> logger) + { + _dbContext = dbContext ?? throw new ArgumentException(nameof(OrderingContext)); + _logger = logger ?? throw new ArgumentException(nameof(ILogger)); + } + + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + TResponse response = default(TResponse); + + try + { + var strategy = _dbContext.Database.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => + { + _logger.LogInformation($"Begin transaction {typeof(TRequest).Name}"); + + await _dbContext.BeginTransactionAsync(); + + response = await next(); + + await _dbContext.CommitTransactionAsync(); + + _logger.LogInformation($"Committed transaction {typeof(TRequest).Name}"); + }); + + return response; + } + catch (Exception) + { + _logger.LogInformation($"Rollback transaction executed {typeof(TRequest).Name}"); + + _dbContext.RollbackTransaction(); + throw; + } + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs index b3c0201b5..602ab9cd5 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; @@ -17,12 +18,16 @@ namespace Ordering.API.Application.IntegrationEvents private readonly Func _integrationEventLogServiceFactory; private readonly IEventBus _eventBus; private readonly OrderingContext _orderingContext; + private readonly IntegrationEventLogContext _eventLogContext; private readonly IIntegrationEventLogService _eventLogService; - public OrderingIntegrationEventService(IEventBus eventBus, OrderingContext orderingContext, - Func integrationEventLogServiceFactory) + public OrderingIntegrationEventService(IEventBus eventBus, + OrderingContext orderingContext, + IntegrationEventLogContext eventLogContext, + Func integrationEventLogServiceFactory) { _orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext)); + _eventLogContext = eventLogContext ?? throw new ArgumentNullException(nameof(eventLogContext)); _integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory)); _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); _eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection()); @@ -37,14 +42,8 @@ namespace Ordering.API.Application.IntegrationEvents private async Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt) { - //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 - await ResilientTransaction.New(_orderingContext) - .ExecuteAsync(async () => { - // Achieving atomicity between original ordering database operation and the IntegrationEventLog thanks to a local transaction - await _orderingContext.SaveChangesAsync(); - await _eventLogService.SaveEventAsync(evt, _orderingContext.Database.CurrentTransaction.GetDbTransaction()); - }); + await _orderingContext.SaveChangesAsync(); + await _eventLogContext.SaveChangesAsync(); } } } diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs index d0dce865f..d40853999 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs @@ -3,6 +3,7 @@ using Autofac.Core; using FluentValidation; using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using Ordering.API.Application.Behaviors; using Ordering.API.Application.DomainEventHandlers.OrderStartedEvent; using Ordering.API.Application.Validations; using Ordering.API.Infrastructure.Behaviors; @@ -53,6 +54,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>)); builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); + builder.RegisterGeneric(typeof(TransactionBehaviour<,>)).As(typeof(IPipelineBehavior<,>)); } } diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index 564acdfb4..f162b09f8 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -1,12 +1,14 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; using Ordering.Infrastructure; using Ordering.Infrastructure.EntityConfigurations; using System; +using System.Data; using System.Threading; using System.Threading.Tasks; @@ -23,6 +25,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure public DbSet OrderStatus { get; set; } private readonly IMediator _mediator; + private IDbContextTransaction _currentTransaction; private OrderingContext(DbContextOptions options) : base (options) { } @@ -60,7 +63,50 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure var result = await base.SaveChangesAsync(); return true; - } + } + + public async Task BeginTransactionAsync() + { + _currentTransaction = _currentTransaction ?? await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted); + } + + public async Task CommitTransactionAsync() + { + try + { + await SaveChangesAsync(); + _currentTransaction?.Commit(); + } + catch + { + RollbackTransaction(); + throw; + } + finally + { + if (_currentTransaction != null) + { + _currentTransaction.Dispose(); + _currentTransaction = null; + } + } + } + + public void RollbackTransaction() + { + try + { + _currentTransaction?.Rollback(); + } + finally + { + if (_currentTransaction != null) + { + _currentTransaction.Dispose(); + _currentTransaction = null; + } + } + } } public class OrderingContextDesignFactory : IDesignTimeDbContextFactory From 9adda02b18a21ec37170d9c0a65a1f608b7af8fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Wed, 3 Oct 2018 23:45:41 +0200 Subject: [PATCH 02/12] Updated OrderingIntegrationEvent svc --- .../OrderingIntegrationEventService.cs | 15 +++++++++++---- .../Ordering.Infrastructure/OrderingContext.cs | 2 ++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs index 602ab9cd5..537dfa971 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs @@ -9,6 +9,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; using System; using System.Data.Common; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; namespace Ordering.API.Application.IntegrationEvents @@ -35,15 +36,21 @@ namespace Ordering.API.Application.IntegrationEvents public async Task PublishThroughEventBusAsync(IntegrationEvent evt) { - await SaveEventAndOrderingContextChangesAsync(evt); + await SaveEventAsync(evt); _eventBus.Publish(evt); await _eventLogService.MarkEventAsPublishedAsync(evt); } - private async Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt) + private async Task SaveEventAsync(IntegrationEvent evt) { - await _orderingContext.SaveChangesAsync(); - await _eventLogContext.SaveChangesAsync(); + var strategy = _orderingContext.Database.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => + { + await _orderingContext.BeginTransactionAsync(); + await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction.GetDbTransaction()); + await _orderingContext.CommitTransactionAsync(); + }); + } } } diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index f162b09f8..ac9ec608f 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -29,6 +29,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure private OrderingContext(DbContextOptions options) : base (options) { } + public IDbContextTransaction GetCurrentTransaction => _currentTransaction; + public OrderingContext(DbContextOptions options, IMediator mediator) : base(options) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); From adafb9abf4cbfec2d02d7506f7ff0344a34175bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Mon, 8 Oct 2018 10:14:23 +0200 Subject: [PATCH 03/12] Send commands for modifying state in IntegrationEventHandlers --- ...SetAwaitingValidationOrderStatusCommand.cs | 21 +++++++ ...tingValidationOrderStatusCommandHandler.cs | 52 +++++++++++++++++ .../Commands/SetPaidOrderStatusCommand.cs | 21 +++++++ .../SetPaidOrderStatusCommandHandler.cs | 55 ++++++++++++++++++ .../SetStockConfirmedOrderStatusCommand.cs | 21 +++++++ ...StockConfirmedOrderStatusCommandHandler.cs | 55 ++++++++++++++++++ .../SetStockRejectedOrderStatusCommand.cs | 25 +++++++++ ...tStockRejectedOrderStatusCommandHandler.cs | 56 +++++++++++++++++++ ...ePeriodConfirmedIntegrationEventHandler.cs | 15 ++--- ...derPaymentFailedIntegrationEventHandler.cs | 16 +++--- ...rPaymentSuccededIntegrationEventHandler.cs | 19 +++---- ...erStockConfirmedIntegrationEventHandler.cs | 19 +++---- ...derStockRejectedIntegrationEventHandler.cs | 18 +++--- 13 files changed, 347 insertions(+), 46 deletions(-) create mode 100644 src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommand.cs create mode 100644 src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs create mode 100644 src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommand.cs create mode 100644 src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs create mode 100644 src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommand.cs create mode 100644 src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs create mode 100644 src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommand.cs create mode 100644 src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommand.cs new file mode 100644 index 000000000..2007b95c6 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommand.cs @@ -0,0 +1,21 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + public class SetAwaitingValidationOrderStatusCommand : IRequest + { + + [DataMember] + public int OrderNumber { get; private set; } + + public SetAwaitingValidationOrderStatusCommand(int orderNumber) + { + OrderNumber = orderNumber; + } + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs new file mode 100644 index 000000000..cee307ca2 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs @@ -0,0 +1,52 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + // Regular CommandHandler + public class SetAwaitingValidationOrderStatusCommandHandler : IRequestHandler + { + private readonly IOrderRepository _orderRepository; + + public SetAwaitingValidationOrderStatusCommandHandler(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + /// + /// Handler which processes the command when + /// graceperiod has finished + /// + /// + /// + public async Task Handle(SetAwaitingValidationOrderStatusCommand command, CancellationToken cancellationToken) + { + var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); + if(orderToUpdate == null) + { + return false; + } + + orderToUpdate.SetAwaitingValidationStatus(); + return await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + } + } + + + // Use for Idempotency in Command process + public class SetAwaitingValidationIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler + { + public SetAwaitingValidationIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + { + } + + protected override bool CreateResultForDuplicateRequest() + { + return true; // Ignore duplicate requests for processing order. + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommand.cs new file mode 100644 index 000000000..12bab9ac5 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommand.cs @@ -0,0 +1,21 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + public class SetPaidOrderStatusCommand : IRequest + { + + [DataMember] + public int OrderNumber { get; private set; } + + public SetPaidOrderStatusCommand(int orderNumber) + { + OrderNumber = orderNumber; + } + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs new file mode 100644 index 000000000..211e568cb --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs @@ -0,0 +1,55 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + // Regular CommandHandler + public class SetPaidOrderStatusCommandHandler : IRequestHandler + { + private readonly IOrderRepository _orderRepository; + + public SetPaidOrderStatusCommandHandler(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + /// + /// Handler which processes the command when + /// Shipment service confirms the payment + /// + /// + /// + public async Task Handle(SetPaidOrderStatusCommand command, CancellationToken cancellationToken) + { + // Simulate a work time for validating the payment + await Task.Delay(10000); + + var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); + if(orderToUpdate == null) + { + return false; + } + + orderToUpdate.SetPaidStatus(); + return await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + } + } + + + // Use for Idempotency in Command process + public class SetPaidIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler + { + public SetPaidIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + { + } + + protected override bool CreateResultForDuplicateRequest() + { + return true; // Ignore duplicate requests for processing order. + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommand.cs new file mode 100644 index 000000000..74f002e21 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommand.cs @@ -0,0 +1,21 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + public class SetStockConfirmedOrderStatusCommand : IRequest + { + + [DataMember] + public int OrderNumber { get; private set; } + + public SetStockConfirmedOrderStatusCommand(int orderNumber) + { + OrderNumber = orderNumber; + } + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs new file mode 100644 index 000000000..4e1bc6185 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs @@ -0,0 +1,55 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + // Regular CommandHandler + public class SetStockConfirmedOrderStatusCommandHandler : IRequestHandler + { + private readonly IOrderRepository _orderRepository; + + public SetStockConfirmedOrderStatusCommandHandler(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + /// + /// Handler which processes the command when + /// Stock service confirms the request + /// + /// + /// + public async Task Handle(SetStockConfirmedOrderStatusCommand command, CancellationToken cancellationToken) + { + // Simulate a work time for confirming the stock + await Task.Delay(10000); + + var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); + if(orderToUpdate == null) + { + return false; + } + + orderToUpdate.SetStockConfirmedStatus(); + return await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + } + } + + + // Use for Idempotency in Command process + public class SetStockConfirmedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler + { + public SetStockConfirmedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + { + } + + protected override bool CreateResultForDuplicateRequest() + { + return true; // Ignore duplicate requests for processing order. + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommand.cs new file mode 100644 index 000000000..c2293c0aa --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommand.cs @@ -0,0 +1,25 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + public class SetStockRejectedOrderStatusCommand : IRequest + { + + [DataMember] + public int OrderNumber { get; private set; } + + [DataMember] + public List OrderStockItems { get; private set; } + + public SetStockRejectedOrderStatusCommand(int orderNumber, List orderStockItems) + { + OrderNumber = orderNumber; + OrderStockItems = orderStockItems; + } + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs new file mode 100644 index 000000000..2b7a72526 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs @@ -0,0 +1,56 @@ +using MediatR; +using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.API.Application.Commands +{ + // Regular CommandHandler + public class SetStockRejectedOrderStatusCommandHandler : IRequestHandler + { + private readonly IOrderRepository _orderRepository; + + public SetStockRejectedOrderStatusCommandHandler(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + /// + /// Handler which processes the command when + /// Stock service rejects the request + /// + /// + /// + public async Task Handle(SetStockRejectedOrderStatusCommand command, CancellationToken cancellationToken) + { + // Simulate a work time for rejecting the stock + await Task.Delay(10000); + + var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); + if(orderToUpdate == null) + { + return false; + } + + orderToUpdate.SetCancelledStatusWhenStockIsRejected(command.OrderStockItems); + + return await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + } + } + + + // Use for Idempotency in Command process + public class SetStockRejectedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler + { + public SetStockRejectedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) + { + } + + protected override bool CreateResultForDuplicateRequest() + { + return true; // Ignore duplicate requests for processing order. + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs index c51619ff6..f8dcc6edb 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs @@ -1,5 +1,7 @@ -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using MediatR; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; +using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationEvents.Events; using System.Threading.Tasks; @@ -7,11 +9,11 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling { public class GracePeriodConfirmedIntegrationEventHandler : IIntegrationEventHandler { - private readonly IOrderRepository _orderRepository; + private readonly IMediator _mediator; - public GracePeriodConfirmedIntegrationEventHandler(IOrderRepository orderRepository) + public GracePeriodConfirmedIntegrationEventHandler(IMediator mediator) { - _orderRepository = orderRepository; + _mediator = mediator; } /// @@ -24,9 +26,8 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling /// public async Task Handle(GracePeriodConfirmedIntegrationEvent @event) { - var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId); - orderToUpdate.SetAwaitingValidationStatus(); - await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId); + await _mediator.Send(command); } } } diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs index 259b7ec34..5f4fc28e1 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs @@ -1,27 +1,27 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling { + using MediatR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; + using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationEvents.Events; + using System; using System.Threading.Tasks; public class OrderPaymentFailedIntegrationEventHandler : IIntegrationEventHandler { - private readonly IOrderRepository _orderRepository; + private readonly IMediator _mediator; - public OrderPaymentFailedIntegrationEventHandler(IOrderRepository orderRepository) + public OrderPaymentFailedIntegrationEventHandler(IMediator mediator) { - _orderRepository = orderRepository; + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); } public async Task Handle(OrderPaymentFailedIntegrationEvent @event) { - var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId); - - orderToUpdate.SetCancelledStatus(); - - await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + var command = new CancelOrderCommand(@event.OrderId); + await _mediator.Send(command); } } } diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs index 1dbe59e10..6c201d77e 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs @@ -1,30 +1,27 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling { + using MediatR; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; + using Ordering.API.Application.Commands; using Ordering.API.Application.IntegrationEvents.Events; + using System; using System.Threading.Tasks; public class OrderPaymentSuccededIntegrationEventHandler : IIntegrationEventHandler { - private readonly IOrderRepository _orderRepository; + private readonly IMediator _mediator; - public OrderPaymentSuccededIntegrationEventHandler(IOrderRepository orderRepository) + public OrderPaymentSuccededIntegrationEventHandler(IMediator mediator) { - _orderRepository = orderRepository; + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); } public async Task Handle(OrderPaymentSuccededIntegrationEvent @event) { - // Simulate a work time for validating the payment - await Task.Delay(10000); - - var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId); - - orderToUpdate.SetPaidStatus(); - - await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + var command = new SetPaidOrderStatusCommand(@event.OrderId); + await _mediator.Send(command); } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs index c08554066..c5561508b 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs @@ -4,27 +4,24 @@ using System.Threading.Tasks; using Events; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; + using MediatR; + using System; + using Ordering.API.Application.Commands; public class OrderStockConfirmedIntegrationEventHandler : IIntegrationEventHandler { - private readonly IOrderRepository _orderRepository; + private readonly IMediator _mediator; - public OrderStockConfirmedIntegrationEventHandler(IOrderRepository orderRepository) + public OrderStockConfirmedIntegrationEventHandler(IMediator mediator) { - _orderRepository = orderRepository; + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); } public async Task Handle(OrderStockConfirmedIntegrationEvent @event) { - // Simulate a work time for confirming the stock - await Task.Delay(10000); - - var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId); - - orderToUpdate.SetStockConfirmedStatus(); - - await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + var command = new SetStockConfirmedOrderStatusCommand(@event.OrderId); + await _mediator.Send(command); } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs index c70eba187..af7d98f74 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs @@ -5,27 +5,27 @@ using Events; using System.Linq; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; + using MediatR; + using Ordering.API.Application.Commands; public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandler { - private readonly IOrderRepository _orderRepository; + private readonly IMediator _mediator; - public OrderStockRejectedIntegrationEventHandler(IOrderRepository orderRepository) + public OrderStockRejectedIntegrationEventHandler(IMediator mediator) { - _orderRepository = orderRepository; + _mediator = mediator; } public async Task Handle(OrderStockRejectedIntegrationEvent @event) { - var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId); - var orderStockRejectedItems = @event.OrderStockItems .FindAll(c => !c.HasStock) - .Select(c => c.ProductId); - - orderToUpdate.SetCancelledStatusWhenStockIsRejected(orderStockRejectedItems); + .Select(c => c.ProductId) + .ToList(); - await _orderRepository.UnitOfWork.SaveEntitiesAsync(); + var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems); + await _mediator.Send(command); } } } \ No newline at end of file From 681111221fe9a54d3db3f4fb713ec14b6ed5fc3a Mon Sep 17 00:00:00 2001 From: Erik Pique Date: Tue, 9 Oct 2018 10:13:20 +0200 Subject: [PATCH 04/12] [BUG] When subscribing more than 1 different event handlers for the same event type #645 https://github.com/dotnet-architecture/eShopOnContainers/issues/645 --- .../InMemoryEventBusSubscriptionsManager.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs index 88be8cf96..e789081f3 100644 --- a/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs +++ b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs @@ -1,10 +1,8 @@ -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; -using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; -using System.Text; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus { @@ -37,8 +35,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus where TH : IIntegrationEventHandler { var eventName = GetEventKey(); + DoAddSubscription(typeof(TH), eventName, isDynamic: false); - _eventTypes.Add(typeof(T)); + + if (!_eventTypes.Contains(typeof(T))) + { + _eventTypes.Add(typeof(T)); + } } private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic) From 4e9716667a0dba66625df38dac0e906afe291403 Mon Sep 17 00:00:00 2001 From: Erik Pique Date: Tue, 9 Oct 2018 13:10:52 +0200 Subject: [PATCH 05/12] [Bug] Deserialization of IntegrationEvent #667 https://github.com/dotnet-architecture/eShopOnContainers/issues/667 --- .../EventBus/EventBus/Events/IntegrationEvent.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs index e01a7aaa8..ef09911fe 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs @@ -1,4 +1,5 @@ using System; +using Newtonsoft.Json; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events { @@ -10,7 +11,17 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events CreationDate = DateTime.UtcNow; } - public Guid Id { get; } - public DateTime CreationDate { get; } + [JsonConstructor] + public IntegrationEvent(Guid id, DateTime createDate) + { + Id = id; + CreationDate = createDate; + } + + [JsonProperty] + public Guid Id { get; private set; } + + [JsonProperty] + public DateTime CreationDate { get; private set; } } } From 641b73222468321f3c0e6a787ada20f948769037 Mon Sep 17 00:00:00 2001 From: Erik Pique Date: Wed, 10 Oct 2018 10:55:00 +0200 Subject: [PATCH 06/12] [BUG] After explicit logout, hit on login, then it is automatically recognized instead of showing the login page #626 https://github.com/dotnet-architecture/eShopOnContainers/issues/626 --- .../Controllers/AccountController.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Services/Identity/Identity.API/Controllers/AccountController.cs b/src/Services/Identity/Identity.API/Controllers/AccountController.cs index 79e9c247e..e638dc614 100644 --- a/src/Services/Identity/Identity.API/Controllers/AccountController.cs +++ b/src/Services/Identity/Identity.API/Controllers/AccountController.cs @@ -1,4 +1,9 @@ -using IdentityModel; +using System; +using System.Linq; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using IdentityModel; using IdentityServer4; using IdentityServer4.Models; using IdentityServer4.Services; @@ -11,11 +16,6 @@ using Microsoft.eShopOnContainers.Services.Identity.API.Models; using Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels; using Microsoft.eShopOnContainers.Services.Identity.API.Services; using Microsoft.Extensions.Logging; -using System; -using System.Linq; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers { @@ -92,7 +92,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers }; await _loginService.SignIn(user); - + // make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint if (_interaction.IsValidReturnUrl(model.ReturnUrl)) { @@ -113,7 +113,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers return View(vm); } - async Task BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context) + private async Task BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context) { var allowLocal = true; if (context?.ClientId != null) @@ -132,7 +132,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers }; } - async Task BuildLoginViewModelAsync(LoginViewModel model) + private async Task BuildLoginViewModelAsync(LoginViewModel model) { var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); var vm = await BuildLoginViewModelAsync(model.ReturnUrl, context); @@ -193,7 +193,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers try { - + // hack: try/catch to handle social providers that throw await HttpContext.SignOutAsync(idp, new AuthenticationProperties { @@ -209,6 +209,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers // delete authentication cookie await HttpContext.SignOutAsync(); + await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); + // set this so UI rendering sees an anonymous user HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); From 74924d975db8731f6ada55d923c2a2045f227980 Mon Sep 17 00:00:00 2001 From: Erik Pique Date: Thu, 11 Oct 2018 16:55:27 +0200 Subject: [PATCH 07/12] Token lifetime handling #118 https://github.com/dotnet-architecture/eShopOnContainers/issues/118 --- .../Controllers/AccountController.cs | 31 ++++++++++++------- .../Identity.API/Services/EFLoginService.cs | 20 ++++++++---- .../Identity.API/Services/ILoginService.cs | 5 +++ 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/Services/Identity/Identity.API/Controllers/AccountController.cs b/src/Services/Identity/Identity.API/Controllers/AccountController.cs index 79e9c247e..85d17228c 100644 --- a/src/Services/Identity/Identity.API/Controllers/AccountController.cs +++ b/src/Services/Identity/Identity.API/Controllers/AccountController.cs @@ -1,4 +1,9 @@ -using IdentityModel; +using System; +using System.Linq; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using IdentityModel; using IdentityServer4; using IdentityServer4.Models; using IdentityServer4.Services; @@ -11,11 +16,6 @@ using Microsoft.eShopOnContainers.Services.Identity.API.Models; using Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels; using Microsoft.eShopOnContainers.Services.Identity.API.Services; using Microsoft.Extensions.Logging; -using System; -using System.Linq; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers { @@ -79,9 +79,16 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers if (ModelState.IsValid) { var user = await _loginService.FindByUsername(model.Email); + if (await _loginService.ValidateCredentials(user, model.Password)) { - AuthenticationProperties props = null; + var props = new AuthenticationProperties + { + ExpiresUtc = DateTimeOffset.UtcNow.AddHours(2), + AllowRefresh = true, + RedirectUri = model.ReturnUrl + }; + if (model.RememberMe) { props = new AuthenticationProperties @@ -91,8 +98,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers }; }; - await _loginService.SignIn(user); - + await _loginService.SignInAsync(user, props); + // make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint if (_interaction.IsValidReturnUrl(model.ReturnUrl)) { @@ -113,7 +120,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers return View(vm); } - async Task BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context) + private async Task BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context) { var allowLocal = true; if (context?.ClientId != null) @@ -132,7 +139,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers }; } - async Task BuildLoginViewModelAsync(LoginViewModel model) + private async Task BuildLoginViewModelAsync(LoginViewModel model) { var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); var vm = await BuildLoginViewModelAsync(model.ReturnUrl, context); @@ -193,7 +200,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers try { - + // hack: try/catch to handle social providers that throw await HttpContext.SignOutAsync(idp, new AuthenticationProperties { diff --git a/src/Services/Identity/Identity.API/Services/EFLoginService.cs b/src/Services/Identity/Identity.API/Services/EFLoginService.cs index 63c4d4b7e..f3a9d5a03 100644 --- a/src/Services/Identity/Identity.API/Services/EFLoginService.cs +++ b/src/Services/Identity/Identity.API/Services/EFLoginService.cs @@ -1,15 +1,17 @@ -using Microsoft.AspNetCore.Identity; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; using Microsoft.eShopOnContainers.Services.Identity.API.Models; -using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Identity.API.Services { public class EFLoginService : ILoginService { - UserManager _userManager; - SignInManager _signInManager; + private UserManager _userManager; + private SignInManager _signInManager; - public EFLoginService(UserManager userManager, SignInManager signInManager) { + public EFLoginService(UserManager userManager, SignInManager signInManager) + { _userManager = userManager; _signInManager = signInManager; } @@ -24,8 +26,14 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Services return await _userManager.CheckPasswordAsync(user, password); } - public Task SignIn(ApplicationUser user) { + public Task SignIn(ApplicationUser user) + { return _signInManager.SignInAsync(user, true); } + + public Task SignInAsync(ApplicationUser user, AuthenticationProperties properties, string authenticationMethod = null) + { + return _signInManager.SignInAsync(user, properties, authenticationMethod); + } } } diff --git a/src/Services/Identity/Identity.API/Services/ILoginService.cs b/src/Services/Identity/Identity.API/Services/ILoginService.cs index 7bff7f272..8a977205b 100644 --- a/src/Services/Identity/Identity.API/Services/ILoginService.cs +++ b/src/Services/Identity/Identity.API/Services/ILoginService.cs @@ -1,11 +1,16 @@ using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; namespace Microsoft.eShopOnContainers.Services.Identity.API.Services { public interface ILoginService { Task ValidateCredentials(T user, string password); + Task FindByUsername(string user); + Task SignIn(T user); + + Task SignInAsync(T user, AuthenticationProperties properties, string authenticationMethod = null); } } From 24bed0aa33a9c9cd6c6de16726200b299e954cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Thu, 11 Oct 2018 17:16:31 +0200 Subject: [PATCH 08/12] Send IntegrationEvents after committing transactions --- .../EventBusRabbitMQ/EventBusRabbitMQ.cs | 4 +- .../EventBusServiceBus/EventBusServiceBus.cs | 6 ++- .../IntegrationEventLogEF/EventStateEnum.cs | 5 +- .../IntegrationEventLogEntry.cs | 13 ++++- .../Services/IIntegrationEventLogService.cs | 5 +- .../Services/IntegrationEventLogService.cs | 47 +++++++++++++++++-- .../CatalogIntegrationEventService.cs | 13 +++-- src/Services/Catalog/Catalog.API/Startup.cs | 6 ++- .../Behaviors/TransactionBehaviour.cs | 9 +++- .../Commands/CreateOrderCommandHandler.cs | 13 ++++- .../OrderCancelledDomainEventHandler.cs | 2 +- ...dToAwaitingValidationDomainEventHandler.cs | 2 +- ...erStatusChangedToPaidDomainEventHandler.cs | 2 +- .../OrderShippedDomainEventHandler.cs | 2 +- ...egateWhenOrderStartedDomainEventHandler.cs | 2 +- ...angedToStockConfirmedDomainEventHandler.cs | 2 +- ...CheckoutAcceptedIntegrationEventHandler.cs | 14 ++---- .../IOrderingIntegrationEventService.cs | 3 +- .../OrderingIntegrationEventService.cs | 30 +++++++----- src/Services/Ordering/Ordering.API/Startup.cs | 13 ++++- 20 files changed, 144 insertions(+), 49 deletions(-) diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index 49a417635..a3b6437ef 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -217,14 +217,16 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ if (subscription.IsDynamic) { var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; + if (handler == null) continue; dynamic eventData = JObject.Parse(message); await handler.Handle(eventData); } else { + var handler = scope.ResolveOptional(subscription.HandlerType); + if (handler == null) continue; var eventType = _subsManager.GetEventTypeByName(eventName); var integrationEvent = JsonConvert.DeserializeObject(message, eventType); - var handler = scope.ResolveOptional(subscription.HandlerType); var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); } diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs index 2cd86669b..d16eb4625 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs @@ -163,14 +163,16 @@ if (subscription.IsDynamic) { var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; + if (handler == null) continue; dynamic eventData = JObject.Parse(message); await handler.Handle(eventData); } else { - var eventType = _subsManager.GetEventTypeByName(eventName); - var integrationEvent = JsonConvert.DeserializeObject(message, eventType); var handler = scope.ResolveOptional(subscription.HandlerType); + if (handler == null) continue; + var eventType = _subsManager.GetEventTypeByName(eventName); + var integrationEvent = JsonConvert.DeserializeObject(message, eventType); var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); } diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs index 3efb78e74..079cf7d7e 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs @@ -7,7 +7,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF public enum EventStateEnum { NotPublished = 0, - Published = 1, - PublishedFailed = 2 + InProgress = 1, + Published = 2, + PublishedFailed = 3 } } diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs index 3cab9e500..b570f4eb8 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Text; using Newtonsoft.Json; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System.Linq; +using System.ComponentModel.DataAnnotations.Schema; namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF { @@ -11,7 +13,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF private IntegrationEventLogEntry() { } public IntegrationEventLogEntry(IntegrationEvent @event) { - EventId = @event.Id; + EventId = @event.Id; CreationTime = @event.CreationDate; EventTypeName = @event.GetType().FullName; Content = JsonConvert.SerializeObject(@event); @@ -20,9 +22,18 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF } public Guid EventId { get; private set; } public string EventTypeName { get; private set; } + [NotMapped] + public string EventTypeShortName => EventTypeName.Split('.')?.Last(); + [NotMapped] + public IntegrationEvent IntegrationEvent { get; private set; } public EventStateEnum State { get; set; } public int TimesSent { get; set; } public DateTime CreationTime { get; private set; } public string Content { get; private set; } + + public void DeserializeJsonContent(Type type) + { + IntegrationEvent = JsonConvert.DeserializeObject(Content, type) as IntegrationEvent; + } } } diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs index ed1f74616..6167d8ae8 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs @@ -9,7 +9,10 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi { public interface IIntegrationEventLogService { + Task> RetrieveEventLogsPendingToPublishAsync(); Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction); - Task MarkEventAsPublishedAsync(IntegrationEvent @event); + Task MarkEventAsPublishedAsync(Guid eventId); + Task MarkEventAsInProgressAsync(Guid eventId); + Task MarkEventAsFailedAsync(Guid eventId); } } diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs index a12309482..0063374c3 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs @@ -1,7 +1,11 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Newtonsoft.Json; using System; +using System.Collections; +using System.Collections.Generic; using System.Data.Common; using System.Linq; using System.Threading.Tasks; @@ -10,12 +14,15 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi { public class IntegrationEventLogService : IIntegrationEventLogService { + private readonly IEventBusSubscriptionsManager _subsManager; private readonly IntegrationEventLogContext _integrationEventLogContext; private readonly DbConnection _dbConnection; - public IntegrationEventLogService(DbConnection dbConnection) + public IntegrationEventLogService(IEventBusSubscriptionsManager subsManager, + DbConnection dbConnection) { _dbConnection = dbConnection ?? throw new ArgumentNullException(nameof(dbConnection)); + _subsManager = subsManager ?? throw new ArgumentNullException(nameof(subsManager)); _integrationEventLogContext = new IntegrationEventLogContext( new DbContextOptionsBuilder() .UseSqlServer(_dbConnection) @@ -23,6 +30,19 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi .Options); } + public async Task> RetrieveEventLogsPendingToPublishAsync() + { + var eventLogsPendingToPublish = await _integrationEventLogContext.IntegrationEventLogs + .Where(e => e.State == EventStateEnum.NotPublished) + .OrderBy(o => o.CreationTime) + .ToListAsync(); + + eventLogsPendingToPublish.ForEach(evtLog => + evtLog.DeserializeJsonContent(_subsManager.GetEventTypeByName(evtLog.EventTypeShortName))); + + return eventLogsPendingToPublish; + } + public Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction) { if (transaction == null) @@ -38,11 +58,28 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi return _integrationEventLogContext.SaveChangesAsync(); } - public Task MarkEventAsPublishedAsync(IntegrationEvent @event) + public Task MarkEventAsPublishedAsync(Guid eventId) + { + return UpdateEventStatus(eventId, EventStateEnum.Published); + } + + public Task MarkEventAsInProgressAsync(Guid eventId) { - var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == @event.Id); - eventLogEntry.TimesSent++; - eventLogEntry.State = EventStateEnum.Published; + return UpdateEventStatus(eventId, EventStateEnum.InProgress); + } + + public Task MarkEventAsFailedAsync(Guid eventId) + { + return UpdateEventStatus(eventId, EventStateEnum.PublishedFailed); + } + + private Task UpdateEventStatus(Guid eventId, EventStateEnum status) + { + var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == eventId); + eventLogEntry.State = status; + + if(status == EventStateEnum.InProgress) + eventLogEntry.TimesSent++; _integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry); diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs index 1b82251e3..8c550bf27 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs @@ -29,9 +29,16 @@ namespace Catalog.API.IntegrationEvents public async Task PublishThroughEventBusAsync(IntegrationEvent evt) { - _eventBus.Publish(evt); - - await _eventLogService.MarkEventAsPublishedAsync(evt); + try + { + await _eventLogService.MarkEventAsInProgressAsync(evt.Id); + _eventBus.Publish(evt); + await _eventLogService.MarkEventAsPublishedAsync(evt.Id); + } + catch (Exception) + { + await _eventLogService.MarkEventAsFailedAsync(evt.Id); + } } public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt) diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 408f870af..9a8d33720 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -232,7 +232,11 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration configuration) { services.AddTransient>( - sp => (DbConnection c) => new IntegrationEventLogService(c)); + sp => + { + var busMgr = sp.GetRequiredService(); + return (DbConnection c) => new IntegrationEventLogService(busMgr, c); + }); services.AddTransient(); diff --git a/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs index d728d83cf..6f9aed9e5 100644 --- a/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs +++ b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; using Microsoft.Extensions.Logging; +using Ordering.API.Application.IntegrationEvents; using System; using System.Collections.Generic; using System.Linq; @@ -14,10 +15,14 @@ namespace Ordering.API.Application.Behaviors { private readonly ILogger> _logger; private readonly OrderingContext _dbContext; + private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; - public TransactionBehaviour(OrderingContext dbContext, ILogger> logger) + public TransactionBehaviour(OrderingContext dbContext, + IOrderingIntegrationEventService orderingIntegrationEventService, + ILogger> logger) { _dbContext = dbContext ?? throw new ArgumentException(nameof(OrderingContext)); + _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentException(nameof(orderingIntegrationEventService)); _logger = logger ?? throw new ArgumentException(nameof(ILogger)); } @@ -39,6 +44,8 @@ namespace Ordering.API.Application.Behaviors await _dbContext.CommitTransactionAsync(); _logger.LogInformation($"Committed transaction {typeof(TRequest).Name}"); + + await _orderingIntegrationEventService.PublishEventsThroughEventBusAsync(); }); return response; diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs index e5f154c0c..9a3035d5c 100644 --- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs @@ -1,6 +1,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands { using Domain.AggregatesModel.OrderAggregate; + using global::Ordering.API.Application.IntegrationEvents; + using global::Ordering.API.Application.IntegrationEvents.Events; using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; @@ -15,17 +17,26 @@ private readonly IOrderRepository _orderRepository; private readonly IIdentityService _identityService; private readonly IMediator _mediator; + private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; // Using DI to inject infrastructure persistence Repositories - public CreateOrderCommandHandler(IMediator mediator, IOrderRepository orderRepository, IIdentityService identityService) + public CreateOrderCommandHandler(IMediator mediator, + IOrderingIntegrationEventService orderingIntegrationEventService, + IOrderRepository orderRepository, + IIdentityService identityService) { _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); } public async Task Handle(CreateOrderCommand message, CancellationToken cancellationToken) { + // Add Integration event to clean the basket + var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(message.UserId); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStartedIntegrationEvent); + // Add/Update the Buyer AggregateRoot // DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root // methods and constructor so validations, invariants and business logic diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs index f8a7b06e5..32967f6a7 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs @@ -41,7 +41,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderCancelled var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); var orderStatusChangedToCancelledIntegrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToCancelledIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToCancelledIntegrationEvent); } } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs index 60efead1b..e1c54af4f 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs @@ -46,7 +46,7 @@ var orderStatusChangedToAwaitingValidationIntegrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent( order.Id, order.OrderStatus.Name, buyer.Name, orderStockList); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToAwaitingValidationIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToAwaitingValidationIntegrationEvent); } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs index 59c1e4708..d3dca202f 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs @@ -51,7 +51,7 @@ buyer.Name, orderStockList); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToPaidIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToPaidIntegrationEvent); } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs index 0bf4cabcd..3be83a2ae 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs @@ -41,7 +41,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderShipped var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); var orderStatusChangedToShippedIntegrationEvent = new OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToShippedIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToShippedIntegrationEvent); } } } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs index 0a8366893..99b2a21a0 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs @@ -59,7 +59,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent .SaveEntitiesAsync(); var orderStatusChangedTosubmittedIntegrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(orderStartedEvent.Order.Id, orderStartedEvent.Order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedTosubmittedIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedTosubmittedIntegrationEvent); _logger.CreateLogger(nameof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)).LogTrace($"Buyer {buyerUpdated.Id} and related payment method were validated or updated for orderId: {orderStartedEvent.Order.Id}."); } diff --git a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs index 910d764cf..e910964e8 100644 --- a/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs @@ -41,7 +41,7 @@ var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString()); var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToStockConfirmedIntegrationEvent); + await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToStockConfirmedIntegrationEvent); } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs index f46c5683c..33f327c6b 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs @@ -11,15 +11,13 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler { private readonly IMediator _mediator; - private readonly ILoggerFactory _logger; - private readonly IOrderingIntegrationEventService _orderingIntegrationEventService; + private readonly ILoggerFactory _logger; public UserCheckoutAcceptedIntegrationEventHandler(IMediator mediator, - ILoggerFactory logger, IOrderingIntegrationEventService orderingIntegrationEventService) + ILoggerFactory logger) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// @@ -34,11 +32,7 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling public async Task Handle(UserCheckoutAcceptedIntegrationEvent eventMsg) { var result = false; - - // Send Integration event to clean basket once basket is converted to Order and before starting with the order creation process - var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(eventMsg.UserId); - await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStartedIntegrationEvent); - + if (eventMsg.RequestId != Guid.Empty) { var createOrderCommand = new CreateOrderCommand(eventMsg.Basket.Items, eventMsg.UserId, eventMsg.UserName, eventMsg.City, eventMsg.Street, diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs index 373bafaa5..05e8f0e4f 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs @@ -5,6 +5,7 @@ namespace Ordering.API.Application.IntegrationEvents { public interface IOrderingIntegrationEventService { - Task PublishThroughEventBusAsync(IntegrationEvent evt); + Task PublishEventsThroughEventBusAsync(); + Task AddAndSaveEventAsync(IntegrationEvent evt); } } diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs index 537dfa971..9c1bd4e1b 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs @@ -34,23 +34,27 @@ namespace Ordering.API.Application.IntegrationEvents _eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection()); } - public async Task PublishThroughEventBusAsync(IntegrationEvent evt) + public async Task PublishEventsThroughEventBusAsync() { - await SaveEventAsync(evt); - _eventBus.Publish(evt); - await _eventLogService.MarkEventAsPublishedAsync(evt); + var pendindLogEvents = await _eventLogService.RetrieveEventLogsPendingToPublishAsync(); + foreach (var logEvt in pendindLogEvents) + { + try + { + await _eventLogService.MarkEventAsInProgressAsync(logEvt.EventId); + _eventBus.Publish(logEvt.IntegrationEvent); + await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId); + } + catch (Exception) + { + await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId); + } + } } - private async Task SaveEventAsync(IntegrationEvent evt) + public async Task AddAndSaveEventAsync(IntegrationEvent evt) { - var strategy = _orderingContext.Database.CreateExecutionStrategy(); - await strategy.ExecuteAsync(async () => - { - await _orderingContext.BeginTransactionAsync(); - await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction.GetDbTransaction()); - await _orderingContext.CommitTransactionAsync(); - }); - + await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction.GetDbTransaction()); } } } diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 42567641a..362df5a47 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -114,6 +114,13 @@ eventBus.Subscribe>(); eventBus.Subscribe>(); eventBus.Subscribe>(); + eventBus.Subscribe>(); + eventBus.Subscribe>(); + eventBus.Subscribe>(); + eventBus.Subscribe>(); + eventBus.Subscribe>(); + eventBus.Subscribe>(); + eventBus.Subscribe>(); } @@ -251,7 +258,11 @@ services.AddSingleton(); services.AddTransient(); services.AddTransient>( - sp => (DbConnection c) => new IntegrationEventLogService(c)); + sp => + { + var busMgr = sp.GetRequiredService(); + return (DbConnection c) => new IntegrationEventLogService(busMgr, c); + }); services.AddTransient(); From e3f8ac6e84a6cc3f94fbdec8439d6942e00d458b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Fri, 12 Oct 2018 15:30:22 +0200 Subject: [PATCH 09/12] Refactoring IntegrationEventLog service --- .../IntegrationEventLogEntry.cs | 4 +++- .../Services/IntegrationEventLogService.cs | 22 +++++++++---------- src/Services/Catalog/Catalog.API/Startup.cs | 6 +---- src/Services/Ordering/Ordering.API/Startup.cs | 15 ++----------- 4 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs index b570f4eb8..e5c3bc9ad 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using System.Linq; using System.ComponentModel.DataAnnotations.Schema; +using System.Reflection; namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF { @@ -31,9 +32,10 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF public DateTime CreationTime { get; private set; } public string Content { get; private set; } - public void DeserializeJsonContent(Type type) + public IntegrationEventLogEntry DeserializeJsonContent(Type type) { IntegrationEvent = JsonConvert.DeserializeObject(Content, type) as IntegrationEvent; + return this; } } } diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs index 0063374c3..2712c5e1c 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs @@ -8,39 +8,39 @@ using System.Collections; using System.Collections.Generic; using System.Data.Common; using System.Linq; +using System.Reflection; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services { public class IntegrationEventLogService : IIntegrationEventLogService { - private readonly IEventBusSubscriptionsManager _subsManager; private readonly IntegrationEventLogContext _integrationEventLogContext; private readonly DbConnection _dbConnection; + private readonly List _eventTypes; - public IntegrationEventLogService(IEventBusSubscriptionsManager subsManager, - DbConnection dbConnection) + public IntegrationEventLogService(DbConnection dbConnection) { _dbConnection = dbConnection ?? throw new ArgumentNullException(nameof(dbConnection)); - _subsManager = subsManager ?? throw new ArgumentNullException(nameof(subsManager)); _integrationEventLogContext = new IntegrationEventLogContext( new DbContextOptionsBuilder() .UseSqlServer(_dbConnection) .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)) .Options); + + _eventTypes = Assembly.Load(Assembly.GetEntryAssembly().FullName) + .GetTypes() + .Where(t => t.Name.EndsWith(nameof(IntegrationEvent))) + .ToList(); } public async Task> RetrieveEventLogsPendingToPublishAsync() { - var eventLogsPendingToPublish = await _integrationEventLogContext.IntegrationEventLogs + return await _integrationEventLogContext.IntegrationEventLogs .Where(e => e.State == EventStateEnum.NotPublished) .OrderBy(o => o.CreationTime) - .ToListAsync(); - - eventLogsPendingToPublish.ForEach(evtLog => - evtLog.DeserializeJsonContent(_subsManager.GetEventTypeByName(evtLog.EventTypeShortName))); - - return eventLogsPendingToPublish; + .Select(e => e.DeserializeJsonContent(_eventTypes.Find(t=> t.Name == e.EventTypeShortName))) + .ToListAsync(); } public Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction) diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 9a8d33720..5f0080c44 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -232,11 +232,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration configuration) { services.AddTransient>( - sp => - { - var busMgr = sp.GetRequiredService(); - return (DbConnection c) => new IntegrationEventLogService(busMgr, c); - }); + sp => (DbConnection c) => new IntegrationEventLogService(c)); services.AddTransient(); diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 362df5a47..f729e6666 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -113,14 +113,7 @@ eventBus.Subscribe>(); eventBus.Subscribe>(); eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); - eventBus.Subscribe>(); + eventBus.Subscribe>(); } @@ -258,11 +251,7 @@ services.AddSingleton(); services.AddTransient(); services.AddTransient>( - sp => - { - var busMgr = sp.GetRequiredService(); - return (DbConnection c) => new IntegrationEventLogService(busMgr, c); - }); + sp => (DbConnection c) => new IntegrationEventLogService(c)); services.AddTransient(); From 9f5e7aee6cf5fea8d3411b2bdce01331e8b78f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Mon, 12 Nov 2018 11:52:37 +0100 Subject: [PATCH 10/12] Fixed ordering unit test --- .../Application/NewOrderCommandHandlerTest.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs b/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs index 7fe02017b..80a0deb25 100644 --- a/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs +++ b/src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; namespace UnitTest.Ordering.Application { + using global::Ordering.API.Application.IntegrationEvents; using global::Ordering.API.Application.Models; using MediatR; using System.Collections; @@ -22,12 +23,14 @@ namespace UnitTest.Ordering.Application private readonly Mock _orderRepositoryMock; private readonly Mock _identityServiceMock; private readonly Mock _mediator; + private readonly Mock _orderingIntegrationEventService; public NewOrderRequestHandlerTest() { _orderRepositoryMock = new Mock(); _identityServiceMock = new Mock(); + _orderingIntegrationEventService = new Mock(); _mediator = new Mock(); } @@ -48,7 +51,7 @@ namespace UnitTest.Ordering.Application _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); //Act - var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); + var handler = new CreateOrderCommandHandler(_mediator.Object, _orderingIntegrationEventService.Object, _orderRepositoryMock.Object, _identityServiceMock.Object); var cltToken = new System.Threading.CancellationToken(); var result = await handler.Handle(fakeOrderCmd, cltToken); From 2593d5d3ad4e25c7012f0a1f1f3ee9786163f237 Mon Sep 17 00:00:00 2001 From: eiximenis Date: Mon, 12 Nov 2018 17:55:22 +0100 Subject: [PATCH 11/12] local k8s docker support --- k8s/deploy-ingress-azure.ps1 | 4 +- k8s/deploy-ingress-dockerlocal.ps1 | 2 + k8s/deploy-ingress.ps1 | 9 +- k8s/deploy.ps1 | 4 + k8s/helm-rbac.yaml | 18 ++ k8s/helm/deploy-all.ps1 | 18 +- k8s/helm/ingress_values_dockerk8s.yaml | 5 + k8s/nginx-ingress/azure/service.yaml | 19 -- k8s/nginx-ingress/cloud-generic.yaml | 21 ++ k8s/nginx-ingress/cm.yaml | Bin 0 -> 526 bytes k8s/nginx-ingress/configmap.yaml | 11 - k8s/nginx-ingress/default-backend.yaml | 52 ---- .../local-dockerk8s/identityapi-cm-fix.yaml | 3 + .../local-dockerk8s/mvc-cm-fix.yaml | 3 + .../local-dockerk8s/mvc-fix.yaml | 39 +++ k8s/nginx-ingress/mandatory.yaml | 238 ++++++++++++++++++ k8s/nginx-ingress/namespace.yaml | 4 - .../patch-service-without-rbac.yaml | 40 --- k8s/nginx-ingress/publish-service-patch.yaml | 7 - k8s/nginx-ingress/service-nodeport.yaml | 22 ++ k8s/nginx-ingress/tcp-services-configmap.yaml | 5 - k8s/nginx-ingress/udp-services-configmap.yaml | 5 - k8s/nginx-ingress/without-rbac.yaml | 61 ----- 23 files changed, 370 insertions(+), 220 deletions(-) create mode 100644 k8s/deploy-ingress-dockerlocal.ps1 create mode 100644 k8s/helm-rbac.yaml create mode 100644 k8s/helm/ingress_values_dockerk8s.yaml delete mode 100644 k8s/nginx-ingress/azure/service.yaml create mode 100644 k8s/nginx-ingress/cloud-generic.yaml create mode 100644 k8s/nginx-ingress/cm.yaml delete mode 100644 k8s/nginx-ingress/configmap.yaml delete mode 100644 k8s/nginx-ingress/default-backend.yaml create mode 100644 k8s/nginx-ingress/local-dockerk8s/identityapi-cm-fix.yaml create mode 100644 k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml create mode 100644 k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml create mode 100644 k8s/nginx-ingress/mandatory.yaml delete mode 100644 k8s/nginx-ingress/namespace.yaml delete mode 100644 k8s/nginx-ingress/patch-service-without-rbac.yaml delete mode 100644 k8s/nginx-ingress/publish-service-patch.yaml create mode 100644 k8s/nginx-ingress/service-nodeport.yaml delete mode 100644 k8s/nginx-ingress/tcp-services-configmap.yaml delete mode 100644 k8s/nginx-ingress/udp-services-configmap.yaml delete mode 100644 k8s/nginx-ingress/without-rbac.yaml diff --git a/k8s/deploy-ingress-azure.ps1 b/k8s/deploy-ingress-azure.ps1 index f93cf437b..d0ff702df 100644 --- a/k8s/deploy-ingress-azure.ps1 +++ b/k8s/deploy-ingress-azure.ps1 @@ -1,3 +1 @@ -kubectl patch deployment -n ingress-nginx nginx-ingress-controller --type=json --patch="$(cat nginx-ingress\publish-service-patch.yaml)" -kubectl apply -f nginx-ingress\azure\service.yaml -kubectl apply -f nginx-ingress\patch-service-without-rbac.yaml \ No newline at end of file +kubectl apply -f nginx-ingress\cloud-generic.yaml \ No newline at end of file diff --git a/k8s/deploy-ingress-dockerlocal.ps1 b/k8s/deploy-ingress-dockerlocal.ps1 new file mode 100644 index 000000000..04ffad763 --- /dev/null +++ b/k8s/deploy-ingress-dockerlocal.ps1 @@ -0,0 +1,2 @@ +kubectl apply -f nginx-ingress\cm.yaml +kubectl apply -f nginx-ingress\cloud-generic.yaml \ No newline at end of file diff --git a/k8s/deploy-ingress.ps1 b/k8s/deploy-ingress.ps1 index 694361bfa..37abcbee2 100644 --- a/k8s/deploy-ingress.ps1 +++ b/k8s/deploy-ingress.ps1 @@ -1,12 +1,5 @@ -kubectl apply -f ingress.yaml - # Deploy nginx-ingress core files -kubectl apply -f nginx-ingress\namespace.yaml -kubectl apply -f nginx-ingress\default-backend.yaml -kubectl apply -f nginx-ingress\configmap.yaml -kubectl apply -f nginx-ingress\tcp-services-configmap.yaml -kubectl apply -f nginx-ingress\udp-services-configmap.yaml -kubectl apply -f nginx-ingress\without-rbac.yaml +kubectl apply -f nginx-ingress\mandatory.yaml diff --git a/k8s/deploy.ps1 b/k8s/deploy.ps1 index f0905096a..abeb12aed 100644 --- a/k8s/deploy.ps1 +++ b/k8s/deploy.ps1 @@ -113,6 +113,7 @@ ExecKube -cmd 'delete configmap internalurls' ExecKube -cmd 'delete configmap urls' ExecKube -cmd 'delete configmap externalcfg' ExecKube -cmd 'delete configmap ocelot' +ExecKube -cmd 'delete -f ingress.yaml' # start sql, rabbitmq, frontend deployments if ($deployInfrastructure) { @@ -204,5 +205,8 @@ ExecKube -cmd 'rollout resume deployments/apigwwm' ExecKube -cmd 'rollout resume deployments/apigwws' ExecKube -cmd 'rollout resume deployments/ordering-signalrhub' +Write-Host "Adding/Updating ingress resource..." -ForegroundColor Yellow +ExecKube -cmd 'apply -f ingress.yaml' + Write-Host "WebSPA is exposed at http://$externalDns, WebMVC at http://$externalDns/webmvc, WebStatus at http://$externalDns/webstatus" -ForegroundColor Yellow diff --git a/k8s/helm-rbac.yaml b/k8s/helm-rbac.yaml new file mode 100644 index 000000000..b6180329a --- /dev/null +++ b/k8s/helm-rbac.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tiller + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: tiller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: tiller + namespace: kube-system \ No newline at end of file diff --git a/k8s/helm/deploy-all.ps1 b/k8s/helm/deploy-all.ps1 index 1239cc7af..08313cdbb 100644 --- a/k8s/helm/deploy-all.ps1 +++ b/k8s/helm/deploy-all.ps1 @@ -8,11 +8,19 @@ Param( [parameter(Mandatory=$false)][bool]$clean=$true, [parameter(Mandatory=$false)][string]$aksName="", [parameter(Mandatory=$false)][string]$aksRg="", - [parameter(Mandatory=$false)][string]$imageTag="latest" -) + [parameter(Mandatory=$false)][string]$imageTag="latest", + [parameter(Mandatory=$false)][bool]$useLocalk8s=$false + ) $dns = $externalDns +$ingressValuesFile="ingress_values.yaml" + +if ($ingressValuesFile) { + $ingressValuesFile="ingress_values_dockerk8s.yaml" + $dns="localhost" +} + if ($externalDns -eq "aks") { if ([string]::IsNullOrEmpty($aksName) -or [string]::IsNullOrEmpty($aksRg)) { Write-Host "Error: When using -dns aks, MUST set -aksName and -aksRg too." -ForegroundColor Red @@ -58,18 +66,18 @@ $charts = ("eshop-common", "apigwmm", "apigwms", "apigwwm", "apigwws", "basket-a if ($deployInfrastructure) { foreach ($infra in $infras) { Write-Host "Installing infrastructure: $infra" -ForegroundColor Green - helm install --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --name="$appName-$infra" $infra + helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --name="$appName-$infra" $infra } } foreach ($chart in $charts) { Write-Host "Installing: $chart" -ForegroundColor Green if ($useCustomRegistry) { - helm install --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart + helm install --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart } else { if ($chart -ne "eshop-common") { # eshop-common is ignored when no secret must be deployed - helm install --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart + helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart } } } diff --git a/k8s/helm/ingress_values_dockerk8s.yaml b/k8s/helm/ingress_values_dockerk8s.yaml new file mode 100644 index 000000000..75597aac9 --- /dev/null +++ b/k8s/helm/ingress_values_dockerk8s.yaml @@ -0,0 +1,5 @@ +ingress: + annotations: + kubernetes.io/ingress.class: "nginx" + ingress.kubernetes.io/ssl-redirect: "false" + nginx.ingress.kubernetes.io/ssl-redirect: "false" diff --git a/k8s/nginx-ingress/azure/service.yaml b/k8s/nginx-ingress/azure/service.yaml deleted file mode 100644 index 8d2f71505..000000000 --- a/k8s/nginx-ingress/azure/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -kind: Service -apiVersion: v1 -metadata: - name: ingress-nginx - namespace: ingress-nginx - labels: - app: ingress-nginx -spec: - externalTrafficPolicy: Local - type: LoadBalancer - selector: - app: ingress-nginx - ports: - - name: http - port: 80 - targetPort: http - - name: https - port: 443 - targetPort: https diff --git a/k8s/nginx-ingress/cloud-generic.yaml b/k8s/nginx-ingress/cloud-generic.yaml new file mode 100644 index 000000000..945441ab8 --- /dev/null +++ b/k8s/nginx-ingress/cloud-generic.yaml @@ -0,0 +1,21 @@ +kind: Service +apiVersion: v1 +metadata: + name: ingress-nginx + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +spec: + externalTrafficPolicy: Local + type: LoadBalancer + selector: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + ports: + - name: http + port: 80 + targetPort: http + - name: https + port: 443 + targetPort: https \ No newline at end of file diff --git a/k8s/nginx-ingress/cm.yaml b/k8s/nginx-ingress/cm.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7818fd15b5c6ea138c70f6b77083b47fcde28e09 GIT binary patch literal 526 zcma)3O$&lh5S(++e-Q7_NGif>=dRr~Kaflxno<4uYIZFXA`;Q(o86tAoqgOxSmf9t zL5+rg6-oqfaK;={OmHApB35jp(UeGrEu>O1j;uZL7W0)eP}Ctu!CS;=LFNxC5vjHC z$lhv3%^t#%Fw;{NGa{jKiyG08QK7^e7`+L0>3fZ3wPdZ?$#9lbr~1!5R3bXM&IbE| zA1zsB{?0-1MHIa&8%`TCKN->S+eb_8inCs+I-+AKAK90o7ddV(R+k&|v8JbP@5--A P*@wft1;0L)SZUQ0cZpZ^ literal 0 HcmV?d00001 diff --git a/k8s/nginx-ingress/configmap.yaml b/k8s/nginx-ingress/configmap.yaml deleted file mode 100644 index 6703fc38e..000000000 --- a/k8s/nginx-ingress/configmap.yaml +++ /dev/null @@ -1,11 +0,0 @@ -kind: ConfigMap -apiVersion: v1 -metadata: - name: nginx-configuration - namespace: ingress-nginx - labels: - app: ingress-nginx -data: - ssl-redirect: "false" - proxy-buffer-size: "128k" - proxy-buffers: "4 256k" diff --git a/k8s/nginx-ingress/default-backend.yaml b/k8s/nginx-ingress/default-backend.yaml deleted file mode 100644 index 64f6f58ad..000000000 --- a/k8s/nginx-ingress/default-backend.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: default-http-backend - labels: - app: default-http-backend - namespace: ingress-nginx -spec: - replicas: 1 - template: - metadata: - labels: - app: default-http-backend - spec: - terminationGracePeriodSeconds: 60 - containers: - - name: default-http-backend - # Any image is permissable as long as: - # 1. It serves a 404 page at / - # 2. It serves 200 on a /healthz endpoint - image: gcr.io/google_containers/defaultbackend:1.4 - livenessProbe: - httpGet: - path: /healthz - port: 8080 - scheme: HTTP - initialDelaySeconds: 30 - timeoutSeconds: 5 - ports: - - containerPort: 8080 - resources: - limits: - cpu: 10m - memory: 20Mi - requests: - cpu: 10m - memory: 20Mi ---- - -apiVersion: v1 -kind: Service -metadata: - name: default-http-backend - namespace: ingress-nginx - labels: - app: default-http-backend -spec: - ports: - - port: 80 - targetPort: 8080 - selector: - app: default-http-backend diff --git a/k8s/nginx-ingress/local-dockerk8s/identityapi-cm-fix.yaml b/k8s/nginx-ingress/local-dockerk8s/identityapi-cm-fix.yaml new file mode 100644 index 000000000..3a3fcf5a5 --- /dev/null +++ b/k8s/nginx-ingress/local-dockerk8s/identityapi-cm-fix.yaml @@ -0,0 +1,3 @@ +data: + mvc_e: http://10.0.75.1/webmvc + \ No newline at end of file diff --git a/k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml b/k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml new file mode 100644 index 000000000..1475deec1 --- /dev/null +++ b/k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml @@ -0,0 +1,3 @@ +data: + urls__IdentityUrl: http://10.0.75.1/identity + urls__mvc: http://10.0.75.1/webmvc diff --git a/k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml b/k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml new file mode 100644 index 000000000..b9ecd4cba --- /dev/null +++ b/k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml @@ -0,0 +1,39 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + annotations: + ingress.kubernetes.io/ssl-redirect: "false" + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/ssl-redirect: "false" + labels: + app: webmvc + name: eshop-webmvc-loopback + namespace: default +spec: + rules: + - http: + paths: + - backend: + serviceName: webmvc + servicePort: http + path: /webmvc +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + annotations: + ingress.kubernetes.io/ssl-redirect: "false" + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/ssl-redirect: "false" + labels: + app: identity-api + name: eshop-identity-api-loopback + namespace: default +spec: + rules: + - http: + paths: + - backend: + serviceName: identity + servicePort: http + path: /identity \ No newline at end of file diff --git a/k8s/nginx-ingress/mandatory.yaml b/k8s/nginx-ingress/mandatory.yaml new file mode 100644 index 000000000..56b1cc3b5 --- /dev/null +++ b/k8s/nginx-ingress/mandatory.yaml @@ -0,0 +1,238 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: ingress-nginx + +--- + +kind: ConfigMap +apiVersion: v1 +metadata: + name: nginx-configuration + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: nginx-ingress-serviceaccount + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: nginx-ingress-clusterrole + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +rules: + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - nodes + - pods + - secrets + verbs: + - list + - watch + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - "extensions" + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - "extensions" + resources: + - ingresses/status + verbs: + - update + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + name: nginx-ingress-role + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +rules: + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + resourceNames: + # Defaults to "-" + # Here: "-" + # This has to be adapted if you change either parameter + # when launching the nginx-ingress-controller. + - "ingress-controller-leader-nginx" + verbs: + - get + - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - apiGroups: + - "" + resources: + - endpoints + verbs: + - get + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: nginx-ingress-role-nisa-binding + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: nginx-ingress-role +subjects: + - kind: ServiceAccount + name: nginx-ingress-serviceaccount + namespace: ingress-nginx + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: nginx-ingress-clusterrole-nisa-binding + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: nginx-ingress-clusterrole +subjects: + - kind: ServiceAccount + name: nginx-ingress-serviceaccount + namespace: ingress-nginx + +--- + +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx-ingress-controller + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + template: + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + annotations: + prometheus.io/port: "10254" + prometheus.io/scrape: "true" + spec: + serviceAccountName: nginx-ingress-serviceaccount + containers: + - name: nginx-ingress-controller + image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.20.0 + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/nginx-configuration + - --publish-service=$(POD_NAMESPACE)/ingress-nginx + - --annotations-prefix=nginx.ingress.kubernetes.io + securityContext: + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + # www-data -> 33 + runAsUser: 33 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + ports: + - name: http + containerPort: 80 + - name: https + containerPort: 443 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 diff --git a/k8s/nginx-ingress/namespace.yaml b/k8s/nginx-ingress/namespace.yaml deleted file mode 100644 index 6878f0be8..000000000 --- a/k8s/nginx-ingress/namespace.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: ingress-nginx diff --git a/k8s/nginx-ingress/patch-service-without-rbac.yaml b/k8s/nginx-ingress/patch-service-without-rbac.yaml deleted file mode 100644 index 919efc389..000000000 --- a/k8s/nginx-ingress/patch-service-without-rbac.yaml +++ /dev/null @@ -1,40 +0,0 @@ -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: nginx-ingress-controller - namespace: ingress-nginx -spec: - replicas: 1 - selector: - matchLabels: - app: ingress-nginx - template: - metadata: - labels: - app: ingress-nginx - spec: - containers: - - name: nginx-ingress-controller - image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.9.0 - args: - - /nginx-ingress-controller - - --default-backend-service=$(POD_NAMESPACE)/default-http-backend - - --configmap=$(POD_NAMESPACE)/nginx-configuration - - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - - --udp-services-configmap=$(POD_NAMESPACE)/udp-services - - --publish-service=$(POD_NAMESPACE)/ingress-nginx - - --annotations-prefix=nginx.ingress.kubernetes.io - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - name: http - containerPort: 80 - - name: https - containerPort: 443 diff --git a/k8s/nginx-ingress/publish-service-patch.yaml b/k8s/nginx-ingress/publish-service-patch.yaml deleted file mode 100644 index f8f52f772..000000000 --- a/k8s/nginx-ingress/publish-service-patch.yaml +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - 'op': 'add', - 'path': '/spec/template/spec/containers/0/args/-', - 'value': '--publish-service=$(POD_NAMESPACE)/ingress-nginx' - } -] diff --git a/k8s/nginx-ingress/service-nodeport.yaml b/k8s/nginx-ingress/service-nodeport.yaml new file mode 100644 index 000000000..dd82ed3ed --- /dev/null +++ b/k8s/nginx-ingress/service-nodeport.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + name: ingress-nginx + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +spec: + type: NodePort + ports: + - name: http + port: 80 + targetPort: 80 + protocol: TCP + - name: https + port: 443 + targetPort: 443 + protocol: TCP + selector: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx diff --git a/k8s/nginx-ingress/tcp-services-configmap.yaml b/k8s/nginx-ingress/tcp-services-configmap.yaml deleted file mode 100644 index a963085d3..000000000 --- a/k8s/nginx-ingress/tcp-services-configmap.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: ConfigMap -apiVersion: v1 -metadata: - name: tcp-services - namespace: ingress-nginx diff --git a/k8s/nginx-ingress/udp-services-configmap.yaml b/k8s/nginx-ingress/udp-services-configmap.yaml deleted file mode 100644 index 1870931a2..000000000 --- a/k8s/nginx-ingress/udp-services-configmap.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: ConfigMap -apiVersion: v1 -metadata: - name: udp-services - namespace: ingress-nginx diff --git a/k8s/nginx-ingress/without-rbac.yaml b/k8s/nginx-ingress/without-rbac.yaml deleted file mode 100644 index 1c46b73eb..000000000 --- a/k8s/nginx-ingress/without-rbac.yaml +++ /dev/null @@ -1,61 +0,0 @@ -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: nginx-ingress-controller - namespace: ingress-nginx -spec: - replicas: 1 - selector: - matchLabels: - app: ingress-nginx - template: - metadata: - labels: - app: ingress-nginx - annotations: - prometheus.io/port: '10254' - prometheus.io/scrape: 'true' - spec: - containers: - - name: nginx-ingress-controller - image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.9.0 - args: - - /nginx-ingress-controller - - --default-backend-service=$(POD_NAMESPACE)/default-http-backend - - --configmap=$(POD_NAMESPACE)/nginx-configuration - - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - - --udp-services-configmap=$(POD_NAMESPACE)/udp-services - - --annotations-prefix=nginx.ingress.kubernetes.io - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - name: http - containerPort: 80 - - name: https - containerPort: 443 - livenessProbe: - failureThreshold: 3 - httpGet: - path: /healthz - port: 10254 - scheme: HTTP - initialDelaySeconds: 10 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 - readinessProbe: - failureThreshold: 3 - httpGet: - path: /healthz - port: 10254 - scheme: HTTP - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 From 408946016815f5f48e5d9d5d35b3587fc3349656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Tue, 13 Nov 2018 09:30:16 +0100 Subject: [PATCH 12/12] Updated Ocelot version --- src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj | 2 +- src/ApiGateways/ApiGw-Base/Startup.cs | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj b/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj index d3b1a049b..821755d9d 100644 --- a/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj +++ b/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj @@ -10,6 +10,6 @@ - + diff --git a/src/ApiGateways/ApiGw-Base/Startup.cs b/src/ApiGateways/ApiGw-Base/Startup.cs index f6a36b59e..da7cd25f4 100644 --- a/src/ApiGateways/ApiGw-Base/Startup.cs +++ b/src/ApiGateways/ApiGw-Base/Startup.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CacheManager.Core; -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging;