using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; using Ordering.Domain.Events; using System; using System.Collections.Generic; using System.Linq; namespace Microsoft.eShopOnContainers.Services.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 Address Address { get; private set; } private int? _buyerId; public OrderStatus OrderStatus { get; private set; } private int _orderStatusId; private string _description; // DDD Patterns comment // Using a private collection field, better for DDD Aggregate's encapsulation // so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection, // but only through the method OrderAggrergateRoot.AddOrderItem() which includes behaviour. private readonly List _orderItems; public IEnumerable OrderItems => _orderItems.AsReadOnly(); // Using List<>.AsReadOnly() // This will create a read only wrapper around the private list so is protected against "external updates". // It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance) //https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx private int? _paymentMethodId; protected Order() { _orderItems = new List(); } public Order(string userId, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null) { _orderItems = new List(); _buyerId = buyerId; _paymentMethodId = paymentMethodId; _orderStatusId = OrderStatus.Submited.Id; _orderDate = DateTime.UtcNow; Address = address; // Add the OrderStarterDomainEvent to the domain events collection // to be raised/dispatched when comitting changes into the Database [ After DbContext.SaveChanges() ] AddOrderStartedDomainEvent(userId, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); } // DDD Patterns comment // This Order AggregateRoot's method "AddOrderitem()" should be the only way to add Items to the Order, // so any behavior (discounts, etc.) and validations are controlled by the AggregateRoot // 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(); if (existingOrderForProduct != null) { //if previous line exist modify it with higher discount and units.. if (discount > existingOrderForProduct.GetCurrentDiscount()) { existingOrderForProduct.SetNewDiscount(discount); existingOrderForProduct.AddUnits(units); } } else { //add validated new order item var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units); _orderItems.Add(orderItem); } } public void SetPaymentId(int id) { _paymentMethodId = id; } public void SetBuyerId(int id) { _buyerId = id; } public void SetOrderStatusId(int id) { _orderStatusId = id; } public void SetOrderStockConfirmed(IEnumerable orderStockNotConfirmedItems = null) { if(orderStockNotConfirmedItems is null) { OrderStatus = OrderStatus.StockValidated; _description = "All the items were confirmed with available stock."; } else { OrderStatus = OrderStatus.Cancelled; var itemsStockNotConfirmedProductNames = OrderItems .Where(c => orderStockNotConfirmedItems.Contains(c.ProductId)) .Select(c => c.GetOrderItemProductName()); var itemsStockNotConfirmedDescription = string.Join(", ", itemsStockNotConfirmedProductNames); _description = $"The product items don't have stock: ({itemsStockNotConfirmedDescription})."; } AddDomainEvent(new OrderStockConfirmedDomainEvent(Id, OrderStatus)); } private void AddOrderStartedDomainEvent(string userId, int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) { var orderStartedDomainEvent = new OrderStartedDomainEvent( this, userId, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); this.AddDomainEvent(orderStartedDomainEvent); } } }