diff --git a/src/Ordering.API/Application/Commands/CreateOrderDraftCommandHandler.cs b/src/Ordering.API/Application/Commands/CreateOrderDraftCommandHandler.cs index f23d39c42..1549a80e5 100644 --- a/src/Ordering.API/Application/Commands/CreateOrderDraftCommandHandler.cs +++ b/src/Ordering.API/Application/Commands/CreateOrderDraftCommandHandler.cs @@ -31,12 +31,12 @@ public static OrderDraftDTO FromOrder(Order order) { OrderItems = order.OrderItems.Select(oi => new OrderItemDTO { - Discount = oi.GetCurrentDiscount(), + Discount = oi.Discount, ProductId = oi.ProductId, - UnitPrice = oi.GetUnitPrice(), - PictureUrl = oi.GetPictureUri(), - Units = oi.GetUnits(), - ProductName = oi.GetOrderItemProductName() + UnitPrice = oi.UnitPrice, + PictureUrl = oi.PictureUrl, + Units = oi.Units, + ProductName = oi.ProductName }), Total = order.GetTotal() }; diff --git a/src/Ordering.API/Application/DomainEventHandlers/OrderCancelledDomainEventHandler.cs b/src/Ordering.API/Application/DomainEventHandlers/OrderCancelledDomainEventHandler.cs index f70d6a265..d66748f2a 100644 --- a/src/Ordering.API/Application/DomainEventHandlers/OrderCancelledDomainEventHandler.cs +++ b/src/Ordering.API/Application/DomainEventHandlers/OrderCancelledDomainEventHandler.cs @@ -25,7 +25,7 @@ public async Task Handle(OrderCancelledDomainEvent domainEvent, CancellationToke OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.Order.Id, OrderStatus.Cancelled); var order = await _orderRepository.GetAsync(domainEvent.Order.Id); - var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value); + var buyer = await _buyerRepository.FindByIdAsync(order.BuyerId.Value); var integrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid); await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent); diff --git a/src/Ordering.API/Application/DomainEventHandlers/OrderShippedDomainEventHandler.cs b/src/Ordering.API/Application/DomainEventHandlers/OrderShippedDomainEventHandler.cs index d94356cdf..52a22f316 100644 --- a/src/Ordering.API/Application/DomainEventHandlers/OrderShippedDomainEventHandler.cs +++ b/src/Ordering.API/Application/DomainEventHandlers/OrderShippedDomainEventHandler.cs @@ -25,7 +25,7 @@ public async Task Handle(OrderShippedDomainEvent domainEvent, CancellationToken OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.Order.Id, OrderStatus.Shipped); var order = await _orderRepository.GetAsync(domainEvent.Order.Id); - var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value); + var buyer = await _buyerRepository.FindByIdAsync(order.BuyerId.Value); var integrationEvent = new OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid); await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent); diff --git a/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs b/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs index d4bc11520..2fa272f1d 100644 --- a/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs +++ b/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs @@ -25,10 +25,10 @@ public async Task Handle(OrderStatusChangedToAwaitingValidationDomainEvent domai OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.OrderId, OrderStatus.AwaitingValidation); var order = await _orderRepository.GetAsync(domainEvent.OrderId); - var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value); + var buyer = await _buyerRepository.FindByIdAsync(order.BuyerId.Value); var orderStockList = domainEvent.OrderItems - .Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits())); + .Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.Units)); var integrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid, orderStockList); await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent); diff --git a/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToPaidDomainEventHandler.cs b/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToPaidDomainEventHandler.cs index 06a8f0b97..6e1c899c0 100644 --- a/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToPaidDomainEventHandler.cs +++ b/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToPaidDomainEventHandler.cs @@ -24,10 +24,10 @@ public async Task Handle(OrderStatusChangedToPaidDomainEvent domainEvent, Cancel OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.OrderId, OrderStatus.Paid); var order = await _orderRepository.GetAsync(domainEvent.OrderId); - var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value); + var buyer = await _buyerRepository.FindByIdAsync(order.BuyerId.Value); var orderStockList = domainEvent.OrderItems - .Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits())); + .Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.Units)); var integrationEvent = new OrderStatusChangedToPaidIntegrationEvent( domainEvent.OrderId, diff --git a/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToStockConfirmedDomainEventHandler.cs b/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToStockConfirmedDomainEventHandler.cs index 19471695b..7ecf3f671 100644 --- a/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToStockConfirmedDomainEventHandler.cs +++ b/src/Ordering.API/Application/DomainEventHandlers/OrderStatusChangedToStockConfirmedDomainEventHandler.cs @@ -25,7 +25,7 @@ public async Task Handle(OrderStatusChangedToStockConfirmedDomainEvent domainEve OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.OrderId, OrderStatus.StockConfirmed); var order = await _orderRepository.GetAsync(domainEvent.OrderId); - var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value); + var buyer = await _buyerRepository.FindByIdAsync(order.BuyerId.Value); var integrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid); await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent); diff --git a/src/Ordering.API/Application/DomainEventHandlers/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs b/src/Ordering.API/Application/DomainEventHandlers/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs index ab221efda..62fb791c4 100644 --- a/src/Ordering.API/Application/DomainEventHandlers/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs +++ b/src/Ordering.API/Application/DomainEventHandlers/UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler.cs @@ -19,8 +19,7 @@ public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler( public async Task Handle(BuyerAndPaymentMethodVerifiedDomainEvent domainEvent, CancellationToken cancellationToken) { var orderToUpdate = await _orderRepository.GetAsync(domainEvent.OrderId); - orderToUpdate.SetBuyerId(domainEvent.Buyer.Id); - orderToUpdate.SetPaymentId(domainEvent.Payment.Id); + orderToUpdate.SetPaymentMethodVerified(domainEvent.Buyer.Id, domainEvent.Payment.Id); OrderingApiTrace.LogOrderPaymentMethodUpdated(_logger, domainEvent.OrderId, nameof(domainEvent.Payment), domainEvent.Payment.Id); } } diff --git a/src/Ordering.API/Application/Queries/OrderQueries.cs b/src/Ordering.API/Application/Queries/OrderQueries.cs index e8a47fa8f..104091384 100644 --- a/src/Ordering.API/Application/Queries/OrderQueries.cs +++ b/src/Ordering.API/Application/Queries/OrderQueries.cs @@ -1,83 +1,53 @@ namespace eShop.Ordering.API.Application.Queries; -public class OrderQueries(NpgsqlDataSource dataSource) +public class OrderQueries(OrderingContext context) : IOrderQueries { public async Task GetOrderAsync(int id) { - using var connection = dataSource.OpenConnection(); - - var result = await connection.QueryAsync(""" - SELECT o."Id" AS ordernumber, o."OrderDate" AS date, o."Description" AS description, o."Address_City" AS city, - o."Address_Country" AS country, o."Address_State" AS state, o."Address_Street" AS street, - o."Address_ZipCode" AS zipcode, o."OrderStatus" AS status, oi."ProductName" AS productname, oi."Units" AS units, - oi."UnitPrice" AS unitprice, oi."PictureUrl" AS pictureurl - FROM ordering.Orders AS o - LEFT JOIN ordering."orderItems" AS oi ON o."Id" = oi."OrderId" - WHERE o."Id" = @id - """, - new { id }); - - if (result.AsList().Count == 0) + var order = await context.Orders + .Include(o => o.OrderItems) + .FirstOrDefaultAsync(o => o.Id == id); + + if (order is null) throw new KeyNotFoundException(); - return MapOrderItems(result); + return new Order + { + ordernumber = order.Id, + date = order.OrderDate, + description = order.Description, + city = order.Address.City, + country = order.Address.Country, + state = order.Address.State, + street = order.Address.Street, + zipcode = order.Address.ZipCode, + status = order.OrderStatus.ToString(), + total = order.GetTotal(), + orderitems = order.OrderItems.Select(oi => new Orderitem + { + productname = oi.ProductName, + units = oi.Units, + unitprice = (double)oi.UnitPrice, + pictureurl = oi.PictureUrl + }).ToList() + }; } public async Task> GetOrdersFromUserAsync(string userId) { - using var connection = dataSource.OpenConnection(); - - return await connection.QueryAsync(""" - SELECT o."Id" AS ordernumber, o."OrderDate" AS date, o."OrderStatus" AS status, SUM(oi."Units" * oi."UnitPrice") AS total - FROM ordering.orders AS o - LEFT JOIN ordering."orderItems" AS oi ON o."Id" = oi."OrderId" - LEFT JOIN ordering.buyers AS ob ON o."BuyerId" = ob."Id" - WHERE ob."IdentityGuid" = @userId - GROUP BY o."Id", o."OrderDate", o."OrderStatus" - ORDER BY o."Id" - """, - new { userId }); - } - - public async Task> GetCardTypesAsync() - { - using var connection = dataSource.OpenConnection(); - - return await connection.QueryAsync("SELECT * FROM ordering.cardtypes"); - } - - private Order MapOrderItems(dynamic result) - { - var order = new Order - { - ordernumber = result[0].ordernumber, - date = result[0].date, - status = result[0].status, - description = result[0].description, - street = result[0].street, - city = result[0].city, - state = result[0].state, - zipcode = result[0].zipcode, - country = result[0].country, - orderitems = new List(), - total = 0 - }; - - foreach (dynamic item in result) - { - var orderitem = new Orderitem + return await context.Orders + .Where(o => o.Buyer.IdentityGuid == userId) + .Select(o => new OrderSummary { - productname = item.productname, - units = item.units, - unitprice = (double)item.unitprice, - pictureurl = item.pictureurl - }; - - order.total += item.units * item.unitprice; - order.orderitems.Add(orderitem); - } - - return order; - } + ordernumber = o.Id, + date = o.OrderDate, + status = o.OrderStatus.ToString(), + total =(double) o.OrderItems.Sum(oi => oi.UnitPrice* oi.Units) + }) + .ToListAsync(); + } + + public async Task> GetCardTypesAsync() => + await context.CardTypes.Select(c=> new CardType { Id = c.Id, Name = c.Name }).ToListAsync(); } diff --git a/src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs index 00e632783..aff7de4b5 100644 --- a/src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -5,21 +5,19 @@ namespace eShop.Ordering.Domain.AggregatesModel.OrderAggregate; public class Order : Entity, IAggregateRoot { - // DDD Patterns comment - // Using private fields, allowed since EF Core 1.1, is a much better encapsulation - // aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections) - private DateTime _orderDate; + public DateTime OrderDate { get; private set; } // Address is a Value Object pattern example persisted as EF Core 2.0 owned entity [Required] public Address Address { get; private set; } - public int? GetBuyerId => _buyerId; - private int? _buyerId; + public int? BuyerId { get; private set; } - public OrderStatus OrderStatus { get; private set; } + public Buyer Buyer { get; } - private string _description; + public OrderStatus OrderStatus { get; private set; } + + public string Description { get; private set; } // Draft orders have this set to true. Currently we don't check anywhere the draft status of an Order, but we could do it if needed #pragma warning disable CS0414 // The field 'Order._isDraft' is assigned but its value is never used @@ -31,9 +29,10 @@ public class Order // so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection, // but only through the method OrderAggregateRoot.AddOrderItem() which includes behavior. private readonly List _orderItems; - public IReadOnlyCollection OrderItems => _orderItems; + + public IReadOnlyCollection OrderItems => _orderItems.AsReadOnly(); - private int? _paymentMethodId; + public int? PaymentId { get; private set; } public static Order NewDraft() { @@ -53,10 +52,10 @@ protected Order() public Order(string userId, string userName, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null) : this() { - _buyerId = buyerId; - _paymentMethodId = paymentMethodId; + BuyerId = buyerId; + PaymentId = paymentMethodId; OrderStatus = OrderStatus.Submitted; - _orderDate = DateTime.UtcNow; + OrderDate = DateTime.UtcNow; Address = address; // Add the OrderStarterDomainEvent to the domain events collection @@ -71,14 +70,12 @@ public Order(string userId, string userName, Address address, int cardTypeId, st // in order to maintain consistency between the whole Aggregate. public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, string pictureUrl, int units = 1) { - var existingOrderForProduct = _orderItems.Where(o => o.ProductId == productId) - .SingleOrDefault(); + var existingOrderForProduct = _orderItems.SingleOrDefault(o => o.ProductId == productId); if (existingOrderForProduct != null) { //if previous line exist modify it with higher discount and units.. - - if (discount > existingOrderForProduct.GetCurrentDiscount()) + if (discount > existingOrderForProduct.Discount) { existingOrderForProduct.SetNewDiscount(discount); } @@ -88,22 +85,17 @@ public void AddOrderItem(int productId, string productName, decimal unitPrice, d else { //add validated new order item - var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units); _orderItems.Add(orderItem); } } - public void SetPaymentId(int id) + public void SetPaymentMethodVerified(int buyerId, int paymentId) { - _paymentMethodId = id; + BuyerId = buyerId; + PaymentId = paymentId; } - - public void SetBuyerId(int id) - { - _buyerId = id; - } - + public void SetAwaitingValidationStatus() { if (OrderStatus == OrderStatus.Submitted) @@ -120,7 +112,7 @@ public void SetStockConfirmedStatus() AddDomainEvent(new OrderStatusChangedToStockConfirmedDomainEvent(Id)); OrderStatus = OrderStatus.StockConfirmed; - _description = "All the items were confirmed with available stock."; + Description = "All the items were confirmed with available stock."; } } @@ -131,7 +123,7 @@ public void SetPaidStatus() AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems)); OrderStatus = OrderStatus.Paid; - _description = "The payment was performed at a simulated \"American Bank checking bank account ending on XX35071\""; + Description = "The payment was performed at a simulated \"American Bank checking bank account ending on XX35071\""; } } @@ -143,7 +135,7 @@ public void SetShippedStatus() } OrderStatus = OrderStatus.Shipped; - _description = "The order was shipped."; + Description = "The order was shipped."; AddDomainEvent(new OrderShippedDomainEvent(this)); } @@ -156,7 +148,7 @@ public void SetCancelledStatus() } OrderStatus = OrderStatus.Cancelled; - _description = $"The order was cancelled."; + Description = $"The order was cancelled."; AddDomainEvent(new OrderCancelledDomainEvent(this)); } @@ -168,10 +160,10 @@ public void SetCancelledStatusWhenStockIsRejected(IEnumerable orderStockRej var itemsStockRejectedProductNames = OrderItems .Where(c => orderStockRejectedItems.Contains(c.ProductId)) - .Select(c => c.GetOrderItemProductName()); + .Select(c => c.ProductName); var itemsStockRejectedDescription = string.Join(", ", itemsStockRejectedProductNames); - _description = $"The product items don't have stock: ({itemsStockRejectedDescription})."; + Description = $"The product items don't have stock: ({itemsStockRejectedDescription})."; } } @@ -190,8 +182,5 @@ private void StatusChangeException(OrderStatus orderStatusToChange) throw new OrderingDomainException($"Is not possible to change the order status from {OrderStatus} to {orderStatusToChange}."); } - public decimal GetTotal() - { - return _orderItems.Sum(o => o.GetUnits() * o.GetUnitPrice()); - } + public decimal GetTotal() => _orderItems.Sum(o => o.Units * o.UnitPrice); } diff --git a/src/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs b/src/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs index 704b098d3..e021bc8c9 100644 --- a/src/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs +++ b/src/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs @@ -5,21 +5,22 @@ namespace eShop.Ordering.Domain.AggregatesModel.OrderAggregate; public class OrderItem : Entity { - // DDD Patterns comment - // Using private fields, allowed since EF Core 1.1, is a much better encapsulation - // aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections) [Required] - private string _productName; - private string _pictureUrl; - private decimal _unitPrice; - private decimal _discount; - private int _units; + public string ProductName { get; private set; } + + public string PictureUrl { get; private set;} + + public decimal UnitPrice { get; private set;} + + public decimal Discount { get; private set; } + + public int Units { get; private set; } public int ProductId { get; private set; } protected OrderItem() { } - public OrderItem(int productId, string productName, decimal unitPrice, decimal discount, string PictureUrl, int units = 1) + public OrderItem(int productId, string productName, decimal unitPrice, decimal discount, string pictureUrl, int units = 1) { if (units <= 0) { @@ -33,32 +34,13 @@ public OrderItem(int productId, string productName, decimal unitPrice, decimal d ProductId = productId; - _productName = productName; - _unitPrice = unitPrice; - _discount = discount; - _units = units; - _pictureUrl = PictureUrl; + ProductName = productName; + UnitPrice = unitPrice; + Discount = discount; + Units = units; + PictureUrl = pictureUrl; } - - public string GetPictureUri() => _pictureUrl; - - public decimal GetCurrentDiscount() - { - return _discount; - } - - public int GetUnits() - { - return _units; - } - - public decimal GetUnitPrice() - { - return _unitPrice; - } - - public string GetOrderItemProductName() => _productName; - + public void SetNewDiscount(decimal discount) { if (discount < 0) @@ -66,7 +48,7 @@ public void SetNewDiscount(decimal discount) throw new OrderingDomainException("Discount is not valid"); } - _discount = discount; + Discount = discount; } public void AddUnits(int units) @@ -76,6 +58,6 @@ public void AddUnits(int units) throw new OrderingDomainException("Invalid units"); } - _units += units; + Units += units; } } diff --git a/src/Ordering.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs b/src/Ordering.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs index c86ecce31..367dd34dd 100644 --- a/src/Ordering.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs +++ b/src/Ordering.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs @@ -15,32 +15,22 @@ public void Configure(EntityTypeBuilder orderConfiguration) orderConfiguration .OwnsOne(o => o.Address); - orderConfiguration - .Property("_buyerId") - .HasColumnName("BuyerId"); - - orderConfiguration - .Property("_orderDate") - .HasColumnName("OrderDate"); - orderConfiguration .Property(o => o.OrderStatus) .HasConversion() .HasMaxLength(30); orderConfiguration - .Property("_paymentMethodId") + .Property(o => o.PaymentId) .HasColumnName("PaymentMethodId"); - orderConfiguration.Property("Description"); - orderConfiguration.HasOne() .WithMany() - .HasForeignKey("_paymentMethodId") + .HasForeignKey(o => o.PaymentId) .OnDelete(DeleteBehavior.Restrict); - orderConfiguration.HasOne() + orderConfiguration.HasOne(o => o.Buyer) .WithMany() - .HasForeignKey("_buyerId"); + .HasForeignKey(o => o.BuyerId); } } diff --git a/src/Ordering.Infrastructure/EntityConfigurations/OrderItemEntityTypeConfiguration.cs b/src/Ordering.Infrastructure/EntityConfigurations/OrderItemEntityTypeConfiguration.cs index da03610be..e35957098 100644 --- a/src/Ordering.Infrastructure/EntityConfigurations/OrderItemEntityTypeConfiguration.cs +++ b/src/Ordering.Infrastructure/EntityConfigurations/OrderItemEntityTypeConfiguration.cs @@ -13,25 +13,5 @@ public void Configure(EntityTypeBuilder orderItemConfiguration) .UseHiLo("orderitemseq"); orderItemConfiguration.Property("OrderId"); - - orderItemConfiguration - .Property("_discount") - .HasColumnName("Discount"); - - orderItemConfiguration - .Property("_productName") - .HasColumnName("ProductName"); - - orderItemConfiguration - .Property("_unitPrice") - .HasColumnName("UnitPrice"); - - orderItemConfiguration - .Property("_units") - .HasColumnName("Units"); - - orderItemConfiguration - .Property("_pictureUrl") - .HasColumnName("PictureUrl"); } }