Browse Source

add order completion flow to ordering api

pull/2129/head
caner.izci 1 year ago
parent
commit
c70b02aa9c
13 changed files with 182 additions and 11 deletions
  1. +12
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/CompleteOrderCommand.cs
  2. +47
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/CompleteOrderCommandHandler.cs
  3. +29
    -0
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCompletedDomainEventHandler.cs
  4. +15
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStatusChangedToCompletedIntegrationEvent.cs
  5. +1
    -1
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs
  6. +3
    -3
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs
  7. +11
    -0
      src/Services/Ordering/Ordering.API/Application/Validations/CompleteOrderCommandValidator.cs
  8. +30
    -0
      src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs
  9. +1
    -0
      src/Services/Ordering/Ordering.API/Program.cs
  10. +2
    -1
      src/Services/Ordering/Ordering.API/Setup/OrderStatus.csv
  11. +13
    -0
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
  12. +7
    -6
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs
  13. +11
    -0
      src/Services/Ordering/Ordering.Domain/Events/OrderCompletedDomainEvent.cs

+ 12
- 0
src/Services/Ordering/Ordering.API/Application/Commands/CompleteOrderCommand.cs View File

@ -0,0 +1,12 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
public class CompleteOrderCommand : IRequest<bool>
{
[DataMember]
public int OrderNumber { get; private set; }
public CompleteOrderCommand(int orderNumber)
{
OrderNumber = orderNumber;
}
}

+ 47
- 0
src/Services/Ordering/Ordering.API/Application/Commands/CompleteOrderCommandHandler.cs View File

@ -0,0 +1,47 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
// Regular CommandHandler
public class CompleteOrderCommandHandler : IRequestHandler<CompleteOrderCommand, bool>
{
private readonly IOrderRepository _orderRepository;
public CompleteOrderCommandHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
/// <summary>
/// Handler which processes the command when
/// shipment company requests to complete order from API
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public async Task<bool> Handle(CompleteOrderCommand command, CancellationToken cancellationToken)
{
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
if (orderToUpdate == null)
{
return false;
}
orderToUpdate.SetCompletedStatus();
return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
}
}
// Use for Idempotency in Command process
public class CompleteOrderIdentifiedCommandHandler : IdentifiedCommandHandler<CompleteOrderCommand, bool>
{
public CompleteOrderIdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager, ILogger<IdentifiedCommandHandler<CompleteOrderCommand, bool>> logger)
: base(mediator, requestManager, logger)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}

+ 29
- 0
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCompletedDomainEventHandler.cs View File

@ -0,0 +1,29 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers;
public partial class OrderCompletedDomainEventHandler : INotificationHandler<OrderCompletedDomainEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly IBuyerRepository _buyerRepository;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
private readonly ILogger _logger;
public OrderCompletedDomainEventHandler(IOrderRepository orderRepository, ILogger<OrderCompletedDomainEventHandler> logger, IBuyerRepository buyerRepository, IOrderingIntegrationEventService orderingIntegrationEventService)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository));
_orderingIntegrationEventService = orderingIntegrationEventService;
}
public async Task Handle(OrderCompletedDomainEvent domainEvent, CancellationToken cancellationToken)
{
OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.Order.Id, nameof(OrderStatus.Completed), OrderStatus.Completed.Id);
var order = await _orderRepository.GetAsync(domainEvent.Order.Id);
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
var integrationEvent = new OrderStatusChangedToCompletedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent);
}
}

+ 15
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStatusChangedToCompletedIntegrationEvent.cs View File

@ -0,0 +1,15 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.Events;
public record OrderStatusChangedToCompletedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public string OrderStatus { get; }
public string BuyerName { get; }
public OrderStatusChangedToCompletedIntegrationEvent(int orderId, string orderStatus, string buyerName)
{
OrderId = orderId;
OrderStatus = orderStatus;
BuyerName = buyerName;
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs View File

@ -3,5 +3,5 @@
public interface IOrderingIntegrationEventService public interface IOrderingIntegrationEventService
{ {
Task PublishEventsThroughEventBusAsync(Guid transactionId); Task PublishEventsThroughEventBusAsync(Guid transactionId);
Task AddAndSaveEventAsync(IntegrationEvent evt);
Task AddAndSaveEventAsync(IntegrationEvent integrationEvent);
} }

+ 3
- 3
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs View File

@ -44,10 +44,10 @@ public class OrderingIntegrationEventService : IOrderingIntegrationEventService
} }
} }
public async Task AddAndSaveEventAsync(IntegrationEvent evt)
public async Task AddAndSaveEventAsync(IntegrationEvent integrationEvent)
{ {
_logger.LogInformation("Enqueuing integration event {IntegrationEventId} to repository ({@IntegrationEvent})", evt.Id, evt);
_logger.LogInformation("Enqueuing integration event {IntegrationEventId} to repository ({@IntegrationEvent})", integrationEvent.Id, integrationEvent);
await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction());
await _eventLogService.SaveEventAsync(integrationEvent, _orderingContext.GetCurrentTransaction());
} }
} }

+ 11
- 0
src/Services/Ordering/Ordering.API/Application/Validations/CompleteOrderCommandValidator.cs View File

@ -0,0 +1,11 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Validations;
public class CompleteOrderCommandValidator : AbstractValidator<CompleteOrderCommand>
{
public CompleteOrderCommandValidator(ILogger<CompleteOrderCommandValidator> logger)
{
RuleFor(order => order.OrderNumber).NotEmpty().WithMessage("No orderId found");
logger.LogTrace("INSTANCE CREATED - {ClassName}", GetType().Name);
}
}

+ 30
- 0
src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs View File

@ -138,4 +138,34 @@ public class OrdersController : ControllerBase
return await _mediator.Send(createOrderDraftCommand); return await _mediator.Send(createOrderDraftCommand);
} }
[Route("complete")]
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CompleteOrderAsync([FromBody] CompleteOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId)
{
bool commandResult = false;
if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty)
{
var requestCompleteOrder = new IdentifiedCommand<CompleteOrderCommand, bool>(command, guid);
_logger.LogInformation(
"Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
requestCompleteOrder.GetGenericTypeName(),
nameof(requestCompleteOrder.Command.OrderNumber),
requestCompleteOrder.Command.OrderNumber,
requestCompleteOrder);
commandResult = await _mediator.Send(requestCompleteOrder);
}
if (!commandResult)
{
return BadRequest();
}
return Ok();
}
} }

+ 1
- 0
src/Services/Ordering/Ordering.API/Program.cs View File

@ -26,6 +26,7 @@ services.AddSingleton<IValidator<CancelOrderCommand>, CancelOrderCommandValidato
services.AddSingleton<IValidator<CreateOrderCommand>, CreateOrderCommandValidator>(); services.AddSingleton<IValidator<CreateOrderCommand>, CreateOrderCommandValidator>();
services.AddSingleton<IValidator<IdentifiedCommand<CreateOrderCommand, bool>>, IdentifiedCommandValidator>(); services.AddSingleton<IValidator<IdentifiedCommand<CreateOrderCommand, bool>>, IdentifiedCommandValidator>();
services.AddSingleton<IValidator<ShipOrderCommand>, ShipOrderCommandValidator>(); services.AddSingleton<IValidator<ShipOrderCommand>, ShipOrderCommandValidator>();
services.AddSingleton<IValidator<CompleteOrderCommand>, CompleteOrderCommandValidator>();
services.AddScoped<IOrderQueries>(sp => new OrderQueries(builder.Configuration.GetConnectionString("OrderingDB"))); services.AddScoped<IOrderQueries>(sp => new OrderQueries(builder.Configuration.GetConnectionString("OrderingDB")));
services.AddScoped<IBuyerRepository, BuyerRepository>(); services.AddScoped<IBuyerRepository, BuyerRepository>();


+ 2
- 1
src/Services/Ordering/Ordering.API/Setup/OrderStatus.csv View File

@ -4,4 +4,5 @@ AwaitingValidation
StockConfirmed StockConfirmed
Paid Paid
Shipped Shipped
Cancelled
Cancelled
Completed

+ 13
- 0
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs View File

@ -190,4 +190,17 @@ public class Order
{ {
return _orderItems.Sum(o => o.GetUnits() * o.GetUnitPrice()); return _orderItems.Sum(o => o.GetUnits() * o.GetUnitPrice());
} }
public void SetCompletedStatus()
{
if (_orderStatusId != OrderStatus.Shipped.Id)
{
StatusChangeException(OrderStatus.Completed);
}
_orderStatusId = OrderStatus.Completed.Id;
_description = $"The order was {OrderStatus.Completed}.";
AddDomainEvent(new OrderCompletedDomainEvent(this));
}
} }

+ 7
- 6
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs View File

@ -5,12 +5,13 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
public class OrderStatus public class OrderStatus
: Enumeration : Enumeration
{ {
public static OrderStatus Submitted = new OrderStatus(1, nameof(Submitted).ToLowerInvariant());
public static OrderStatus AwaitingValidation = new OrderStatus(2, nameof(AwaitingValidation).ToLowerInvariant());
public static OrderStatus StockConfirmed = new OrderStatus(3, nameof(StockConfirmed).ToLowerInvariant());
public static OrderStatus Paid = new OrderStatus(4, nameof(Paid).ToLowerInvariant());
public static OrderStatus Shipped = new OrderStatus(5, nameof(Shipped).ToLowerInvariant());
public static OrderStatus Cancelled = new OrderStatus(6, nameof(Cancelled).ToLowerInvariant());
public static OrderStatus Submitted = new(1, nameof(Submitted).ToLowerInvariant());
public static OrderStatus AwaitingValidation = new(2, nameof(AwaitingValidation).ToLowerInvariant());
public static OrderStatus StockConfirmed = new(3, nameof(StockConfirmed).ToLowerInvariant());
public static OrderStatus Paid = new(4, nameof(Paid).ToLowerInvariant());
public static OrderStatus Shipped = new(5, nameof(Shipped).ToLowerInvariant());
public static OrderStatus Cancelled = new(6, nameof(Cancelled).ToLowerInvariant());
public static OrderStatus Completed = new(7, nameof(Completed).ToLowerInvariant());
public OrderStatus(int id, string name) public OrderStatus(int id, string name)
: base(id, name) : base(id, name)


+ 11
- 0
src/Services/Ordering/Ordering.Domain/Events/OrderCompletedDomainEvent.cs View File

@ -0,0 +1,11 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Events;
public class OrderCompletedDomainEvent : INotification
{
public Order Order { get; }
public OrderCompletedDomainEvent(Order order)
{
Order = order;
}
}

Loading…
Cancel
Save