Adding Integration Event handlers to ordering Api

This commit is contained in:
Christian Arenas 2017-05-15 19:14:37 +02:00
parent 830d0597d7
commit 82fe859584
18 changed files with 256 additions and 82 deletions

View File

@ -88,7 +88,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
var result = new List<OrderItemDTO>(); var result = new List<OrderItemDTO>();
basketItems.ForEach((item) => { basketItems.ForEach((item) => {
result.Add(new OrderItemDTO() { result.Add(new OrderItemDTO() {
ProductId = int.TryParse(item.Id, out int id) ? id : -1, ProductId = int.TryParse(item.ProductId, out int id) ? id : -1,
ProductName = item.ProductName, ProductName = item.ProductName,
PictureUrl = item.PictureUrl, PictureUrl = item.PictureUrl,
UnitPrice = item.UnitPrice, UnitPrice = item.UnitPrice,

View File

@ -43,7 +43,7 @@
// make sure that consistency is preserved across the whole aggregate // make sure that consistency is preserved across the whole aggregate
var address = new Address(message.Street, message.City, message.State, message.Country, message.ZipCode); var address = new Address(message.Street, message.City, message.State, message.Country, message.ZipCode);
var order = new Order(message.UserId, address, message.CardTypeId, message.CardNumber, message.CardSecurityNumber, message.CardHolderName, message.CardExpiration); var order = new Order(message.UserId, address, message.CardTypeId, message.CardNumber, message.CardSecurityNumber, message.CardHolderName, message.CardExpiration);
order.SetOrderStatusId(OrderStatus.Submited.Id); order.SetSubmitedStatus();
foreach (var item in message.OrderItems) foreach (var item in message.OrderItems)
{ {
order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units); order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units);

View File

@ -0,0 +1,46 @@
namespace Ordering.API.Application.DomainEventHandlers.OrderGracePeriodConfirmed
{
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Domain.Events;
using System;
using System.Threading.Tasks;
using Ordering.API.Application.IntegrationCommands.Commands;
using Ordering.API.Application.IntegrationEvents;
using System.Linq;
public class OrderStatusChangedToAwaitingValidationDomainEventHandler
: IAsyncNotificationHandler<OrderStatusChangedToAwaitingValidationDomainEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public OrderStatusChangedToAwaitingValidationDomainEventHandler(
IOrderRepository orderRepository, ILoggerFactory logger,
IOrderingIntegrationEventService orderingIntegrationEventService)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_orderingIntegrationEventService = orderingIntegrationEventService;
}
public async Task Handle(OrderStatusChangedToAwaitingValidationDomainEvent orderStatusChangedToAwaitingValidationDomainEvent)
{
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
_logger.CreateLogger(nameof(OrderStatusChangedToAwaitingValidationDomainEvent))
.LogTrace($"Order with Id: {orderStatusChangedToAwaitingValidationDomainEvent.OrderId} has been successfully updated with " +
$"a status order id: {OrderStatus.AwaitingValidation.Id}");
var orderStockList = orderStatusChangedToAwaitingValidationDomainEvent.OrderItems
.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits()));
var confirmOrderStockEvent = new ConfirmOrderStockCommandMsg(orderStatusChangedToAwaitingValidationDomainEvent.OrderId,
orderStockList);
await _orderingIntegrationEventService.SaveEventAndOrderingContextChangesAsync(confirmOrderStockEvent);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(confirmOrderStockEvent);
}
}
}

View File

@ -0,0 +1,51 @@
namespace Ordering.API.Application.DomainEventHandlers.OrderPaid
{
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Domain.Events;
using System;
using System.Threading.Tasks;
using Ordering.API.Application.IntegrationCommands.Commands;
using Ordering.API.Application.IntegrationEvents;
using System.Linq;
public class OrderStatusChangedToPaidDomainEventHandler
: IAsyncNotificationHandler<OrderStatusChangedToPaidDomainEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public OrderStatusChangedToPaidDomainEventHandler(
IOrderRepository orderRepository, ILoggerFactory logger,
IOrderingIntegrationEventService orderingIntegrationEventService)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_orderingIntegrationEventService = orderingIntegrationEventService;
}
public async Task Handle(OrderStatusChangedToPaidDomainEvent orderStatusChangedToPaidDomainEvent)
{
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
_logger.CreateLogger(nameof(OrderStatusChangedToPaidDomainEventHandler))
.LogTrace($"Order with Id: {orderStatusChangedToPaidDomainEvent.OrderId} has been successfully updated with " +
$"a status order id: {OrderStatus.Paid.Id}");
var orderStockList = orderStatusChangedToPaidDomainEvent.OrderItems
.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits()));
var decrementOrderStockCommandMsg = new DecrementOrderStockCommandMsg(orderStatusChangedToPaidDomainEvent.OrderId,
orderStockList);
await _orderingIntegrationEventService.SaveEventAndOrderingContextChangesAsync(decrementOrderStockCommandMsg);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(decrementOrderStockCommandMsg);
//is it necessary get a DecrementOrderStockSuccessIntegrationEvent/DecrementOrderStockFailedIntegrationEvent before to call ShipOrderCommandMsg???
var shipOrderCommandMsg = new ShipOrderCommandMsg(orderStatusChangedToPaidDomainEvent.OrderId);
await _orderingIntegrationEventService.SaveEventAndOrderingContextChangesAsync(shipOrderCommandMsg);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(shipOrderCommandMsg);
}
}
}

View File

@ -1,44 +0,0 @@
using System.Linq;
namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent
{
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Domain.Events;
using System;
using System.Threading.Tasks;
using Ordering.API.Application.IntegrationCommands.Commands;
using Ordering.API.Application.IntegrationEvents;
public class OrderStatusChangedWhenOrderStockConfirmedDomainEventHandler
: IAsyncNotificationHandler<OrderStockConfirmedDomainEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public OrderStatusChangedWhenOrderStockConfirmedDomainEventHandler(
IOrderRepository orderRepository, ILoggerFactory logger,
IOrderingIntegrationEventService orderingIntegrationEventService)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_orderingIntegrationEventService = orderingIntegrationEventService;
}
public async Task Handle(OrderStockConfirmedDomainEvent orderStockMethodVerifiedDomainEvent)
{
_logger.CreateLogger(nameof(OrderStatusChangedWhenOrderStockConfirmedDomainEventHandler))
.LogTrace($"Order with Id: {orderStockMethodVerifiedDomainEvent.OrderId} has been successfully updated with " +
$"a status order id: { orderStockMethodVerifiedDomainEvent.OrderStatus.Id }");
if (orderStockMethodVerifiedDomainEvent.OrderStatus == OrderStatus.StockValidated)
{
var payOrderCommandMsg = new PayOrderCommandMsg(orderStockMethodVerifiedDomainEvent.OrderId);
await _orderingIntegrationEventService.SaveEventAndOrderingContextChangesAsync(payOrderCommandMsg);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(payOrderCommandMsg);
}
}
}
}

View File

@ -0,0 +1,41 @@
namespace Ordering.API.Application.DomainEventHandlers.OrderStockConfirmed
{
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Domain.Events;
using System;
using System.Threading.Tasks;
using Ordering.API.Application.IntegrationCommands.Commands;
using Ordering.API.Application.IntegrationEvents;
public class OrderStatusChangedToStockConfirmedDomainEventHandler
: IAsyncNotificationHandler<OrderStatusChangedToStockConfirmedDomainEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public OrderStatusChangedToStockConfirmedDomainEventHandler(
IOrderRepository orderRepository, ILoggerFactory logger,
IOrderingIntegrationEventService orderingIntegrationEventService)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_orderingIntegrationEventService = orderingIntegrationEventService;
}
public async Task Handle(OrderStatusChangedToStockConfirmedDomainEvent orderStatusChangedToStockConfirmedDomainEvent)
{
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
_logger.CreateLogger(nameof(OrderStatusChangedToStockConfirmedDomainEventHandler))
.LogTrace($"Order with Id: {orderStatusChangedToStockConfirmedDomainEvent.OrderId} has been successfully updated with " +
$"a status order id: {OrderStatus.StockConfirmed.Id}");
var payOrderCommandMsg = new PayOrderCommandMsg(orderStatusChangedToStockConfirmedDomainEvent.OrderId);
await _orderingIntegrationEventService.SaveEventAndOrderingContextChangesAsync(payOrderCommandMsg);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(payOrderCommandMsg);
}
}
}

View File

@ -6,13 +6,13 @@
public class ConfirmOrderStockCommandMsg : IntegrationEvent public class ConfirmOrderStockCommandMsg : IntegrationEvent
{ {
public int OrderId { get; } public int OrderId { get; }
public IEnumerable<OrderStockItem> OrderStockItem { get; } public IEnumerable<OrderStockItem> OrderStockItems { get; }
public ConfirmOrderStockCommandMsg(int orderId, public ConfirmOrderStockCommandMsg(int orderId,
IEnumerable<OrderStockItem> orderStockItem) IEnumerable<OrderStockItem> orderStockItems)
{ {
OrderId = orderId; OrderId = orderId;
OrderStockItem = orderStockItem; OrderStockItems = orderStockItems;
} }
} }

View File

@ -0,0 +1,18 @@
namespace Ordering.API.Application.IntegrationCommands.Commands
{
using System.Collections.Generic;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class DecrementOrderStockCommandMsg : IntegrationEvent
{
public int OrderId { get; }
public IEnumerable<OrderStockItem> OrderStockItems { get; }
public DecrementOrderStockCommandMsg(int orderId,
IEnumerable<OrderStockItem> orderStockItems)
{
OrderId = orderId;
OrderStockItems = orderStockItems;
}
}
}

View File

@ -0,0 +1,14 @@
namespace Ordering.API.Application.IntegrationCommands.Commands
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class ShipOrderCommandMsg : IntegrationEvent
{
public int OrderId { get; }
public ShipOrderCommandMsg(int orderId)
{
OrderId = orderId;
}
}
}

View File

@ -1,14 +1,23 @@
namespace Ordering.API.Application.IntegrationEvents.EventHandling namespace Ordering.API.Application.IntegrationEvents.EventHandling
{ {
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.IntegrationEvents.Events; using Ordering.API.Application.IntegrationEvents.Events;
using System.Threading.Tasks; using System.Threading.Tasks;
public class OrderPaymentFailedIntegrationEventHandler : public class OrderPaymentFailedIntegrationEventHandler :
IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent> IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>
{ {
private readonly IOrderRepository _orderRepository;
public OrderPaymentFailedIntegrationEventHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task Handle(OrderPaymentFailedIntegrationEvent @event) public async Task Handle(OrderPaymentFailedIntegrationEvent @event)
{ {
//TODO: Cancel Order
} }
} }
} }

View File

@ -1,14 +1,35 @@
namespace Ordering.API.Application.IntegrationEvents.EventHandling namespace Ordering.API.Application.IntegrationEvents.EventHandling
{ {
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.IntegrationEvents.Events; using Ordering.API.Application.IntegrationEvents.Events;
using Ordering.Domain.Exceptions;
using System.Threading.Tasks; using System.Threading.Tasks;
public class OrderPaymentSuccededIntegrationEventHandler : public class OrderPaymentSuccededIntegrationEventHandler :
IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent> IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>
{ {
private readonly IOrderRepository _orderRepository;
public OrderPaymentSuccededIntegrationEventHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task Handle(OrderPaymentSuccededIntegrationEvent @event) public async Task Handle(OrderPaymentSuccededIntegrationEvent @event)
{ {
var order = await _orderRepository.GetAsync(@event.OrderId);
CheckValidSagaId(order);
order.SetPaidStatus();
}
private void CheckValidSagaId(Order orderSaga)
{
if (orderSaga is null)
{
throw new OrderingDomainException("Not able to process order saga event. Reason: no valid orderId");
}
} }
} }
} }

View File

@ -22,7 +22,7 @@
var order = await _orderRepository.GetAsync(@event.OrderId); var order = await _orderRepository.GetAsync(@event.OrderId);
CheckValidSagaId(order); CheckValidSagaId(order);
order.SetOrderStockConfirmed(); order.SetStockConfirmedStatus();
} }
private void CheckValidSagaId(Order orderSaga) private void CheckValidSagaId(Order orderSaga)

View File

@ -28,7 +28,7 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
.FindAll(c => !c.Confirmed) .FindAll(c => !c.Confirmed)
.Select(c => c.ProductId); .Select(c => c.ProductId);
orderToUpdate.SetOrderStockConfirmed(orderStockNotConfirmedItems); orderToUpdate.SetStockConfirmedStatus(orderStockNotConfirmedItems);
} }
private void CheckValidSagaId(Order orderSaga) private void CheckValidSagaId(Order orderSaga)

View File

@ -7,15 +7,11 @@ using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency; using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Ordering.API.Application.Commands; using Ordering.API.Application.Commands;
using Ordering.API.Application.IntegrationCommands.Commands; using Ordering.API.Application.IntegrationCommands.Commands;
using Ordering.API.Application.IntegrationEvents.Events;
using Ordering.Domain.Exceptions; using Ordering.Domain.Exceptions;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ordering.API.Application.IntegrationEvents; using Ordering.API.Application.IntegrationEvents;
using Ordering.API.Application.IntegrationEvents.Events;
namespace Ordering.API.Application.Sagas namespace Ordering.API.Application.Sagas
{ {
@ -27,7 +23,7 @@ namespace Ordering.API.Application.Sagas
/// the opportunity to cancel the order before proceeding /// the opportunity to cancel the order before proceeding
/// with the validations. /// with the validations.
/// </summary> /// </summary>
public class OrderProcessSaga : Saga<Order>, public class OrderProcessSaga : OrderSaga,
IIntegrationEventHandler<ConfirmGracePeriodCommandMsg>, IIntegrationEventHandler<ConfirmGracePeriodCommandMsg>,
IAsyncRequestHandler<CancelOrderCommand, bool> IAsyncRequestHandler<CancelOrderCommand, bool>
{ {
@ -63,17 +59,9 @@ namespace Ordering.API.Application.Sagas
if (orderSaga.OrderStatus != OrderStatus.Cancelled) if (orderSaga.OrderStatus != OrderStatus.Cancelled)
{ {
orderSaga.SetOrderStatusId(OrderStatus.AwaitingValidation.Id); orderSaga.SetAwaitingValidationStatus();
await SaveChangesAsync(); await SaveChangesAsync();
var orderStockList = orderSaga.OrderItems
.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits()));
var confirmOrderStockEvent = new ConfirmOrderStockCommandMsg(orderSaga.Id, orderStockList);
await _orderingIntegrationEventService.SaveEventAndOrderingContextChangesAsync(confirmOrderStockEvent);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(confirmOrderStockEvent);
} }
} }

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
namespace Ordering.API.Application.Sagas
{
public abstract class OrderSaga : Saga<Order>
{
private OrderingContext _orderingContext;
public OrderSaga(OrderingContext orderingContext) : base(orderingContext)
{
_orderingContext = orderingContext;
}
public override Order FindSagaById(int id)
{
var order = _orderingContext.Orders
.Single(c => c.Id == id);
_orderingContext.Entry(order)
.Member("OrderStatus");
return order;
}
public override async Task<bool> SaveChangesAsync()
{
return await _orderingContext.SaveEntitiesAsync();
}
}
}

View File

@ -14,17 +14,13 @@ namespace Ordering.API.Application.Sagas
_dbContext = dbContext; _dbContext = dbContext;
} }
protected TEntity FindSagaById(int id, DbContext context = null) public abstract TEntity FindSagaById(int id);
{
var ctx = context ?? _dbContext;
return ctx.Set<TEntity>().Where(x => x.Id == id).SingleOrDefault();
}
protected async Task<bool> SaveChangesAsync(DbContext context = null) public abstract Task<bool> SaveChangesAsync();
{ //{
var ctx = context ?? _dbContext; // var ctx = context ?? _dbContext;
var result = await ctx.SaveChangesAsync(); // var result = await ctx.SaveChangesAsync();
return result > 0; // return result > 0;
} //}
} }
} }

View File

@ -33,7 +33,7 @@
{ {
context.OrderStatus.Add(OrderStatus.Submited); context.OrderStatus.Add(OrderStatus.Submited);
context.OrderStatus.Add(OrderStatus.AwaitingValidation); context.OrderStatus.Add(OrderStatus.AwaitingValidation);
context.OrderStatus.Add(OrderStatus.StockValidated); context.OrderStatus.Add(OrderStatus.StockConfirmed);
context.OrderStatus.Add(OrderStatus.Paid); context.OrderStatus.Add(OrderStatus.Paid);
context.OrderStatus.Add(OrderStatus.Shipped); context.OrderStatus.Add(OrderStatus.Shipped);
context.OrderStatus.Add(OrderStatus.Cancelled); context.OrderStatus.Add(OrderStatus.Cancelled);

View File

@ -7,7 +7,6 @@
using global::Ordering.API.Application.IntegrationEvents.Events; using global::Ordering.API.Application.IntegrationEvents.Events;
using global::Ordering.API.Infrastructure.Middlewares; using global::Ordering.API.Infrastructure.Middlewares;
using global::Ordering.API.Application.IntegrationCommands.Commands; using global::Ordering.API.Application.IntegrationCommands.Commands;
using global::Ordering.API.Application.IntegrationEvents.Events;
using global::Ordering.API.Application.Sagas; using global::Ordering.API.Application.Sagas;
using Infrastructure; using Infrastructure;
using Infrastructure.Auth; using Infrastructure.Auth;
@ -31,7 +30,6 @@
using System; using System;
using System.Data.Common; using System.Data.Common;
using System.Reflection; using System.Reflection;
using global::Ordering.API.Application.IntegrationEvents.EventHandling;
public class Startup public class Startup
{ {
@ -110,7 +108,7 @@
services.AddTransient<IIdentityService, IdentityService>(); services.AddTransient<IIdentityService, IdentityService>();
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>( services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
sp => (DbConnection c) => new IntegrationEventLogService(c)); sp => (DbConnection c) => new IntegrationEventLogService(c));
var serviceProvider = services.BuildServiceProvider();
services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>(); services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>();
services.AddSingleton<IRabbitMQPersistentConnection>(sp => services.AddSingleton<IRabbitMQPersistentConnection>(sp =>