using MediatR; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; 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; // 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<OrderItem> _orderItems; public IEnumerable<OrderItem> 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() { } public Order(Address address, int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null) { _orderItems = new List<OrderItem>(); _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(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; } private void AddOrderStartedDomainEvent(int cardTypeId, string cardNumber, string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) { var orderStartedDomainEvent = new OrderStartedDomainEvent( this, cardTypeId, cardNumber, cardSecurityNumber, cardHolderName, cardExpiration); this.AddDomainEvent(orderStartedDomainEvent); } } }