From 99d47401c03ded2166eb3af901ccf9dc820f32f6 Mon Sep 17 00:00:00 2001 From: Rafsanul Hasan Date: Thu, 4 Apr 2019 10:21:04 +0600 Subject: [PATCH] make fields readonly --- .../BuyerAggregate/PaymentMethod.cs | 76 ++-- .../AggregatesModel/OrderAggregate/Order.cs | 373 +++++++++--------- .../OrderAggregate/OrderItem.cs | 147 ++++--- 3 files changed, 291 insertions(+), 305 deletions(-) diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs index b307604da..1a1de9701 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs @@ -4,43 +4,41 @@ using System; namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate { - public class PaymentMethod - : Entity - { - private string _alias; - private string _cardNumber; - private string _securityNumber; - private string _cardHolderName; - private DateTime _expiration; - - private int _cardTypeId; - public CardType CardType { get; private set; } - - - protected PaymentMethod() { } - - public PaymentMethod(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration) - { - - _cardNumber = !string.IsNullOrWhiteSpace(cardNumber) ? cardNumber : throw new OrderingDomainException(nameof(cardNumber)); - _securityNumber = !string.IsNullOrWhiteSpace(securityNumber) ? securityNumber : throw new OrderingDomainException(nameof(securityNumber)); - _cardHolderName = !string.IsNullOrWhiteSpace(cardHolderName) ? cardHolderName : throw new OrderingDomainException(nameof(cardHolderName)); - - if (expiration < DateTime.UtcNow) - { - throw new OrderingDomainException(nameof(expiration)); - } - - _alias = alias; - _expiration = expiration; - _cardTypeId = cardTypeId; - } - - public bool IsEqualTo(int cardTypeId, string cardNumber,DateTime expiration) - { - return _cardTypeId == cardTypeId - && _cardNumber == cardNumber - && _expiration == expiration; - } - } + public class PaymentMethod + : Entity + { + private readonly string _alias; + private readonly string _cardNumber; + private readonly string _securityNumber; + private readonly string _cardHolderName; + private readonly DateTime _expiration; + + private readonly int _cardTypeId; + public CardType CardType { get; private set; } + + + protected PaymentMethod() { } + + public PaymentMethod(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration) + { + + _cardNumber = !string.IsNullOrWhiteSpace(cardNumber) ? cardNumber : throw new OrderingDomainException(nameof(cardNumber)); + _securityNumber = !string.IsNullOrWhiteSpace(securityNumber) ? securityNumber : throw new OrderingDomainException(nameof(securityNumber)); + _cardHolderName = !string.IsNullOrWhiteSpace(cardHolderName) ? cardHolderName : throw new OrderingDomainException(nameof(cardHolderName)); + + if (expiration < DateTime.UtcNow) + { + throw new OrderingDomainException(nameof(expiration)); + } + + _alias = alias; + _expiration = expiration; + _cardTypeId = cardTypeId; + } + + public bool IsEqualTo(int cardTypeId, string cardNumber, DateTime expiration) + => _cardTypeId == cardTypeId + && _cardNumber == cardNumber + && _expiration == expiration; + } } diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs index 99b3154d8..c4be924f2 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -7,194 +7,189 @@ 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; - - // Address is a Value Object pattern example persisted as EF Core 2.0 owned entity - public Address Address { get; private set; } - - public int? GetBuyerId => _buyerId; - private int? _buyerId; - - public OrderStatus OrderStatus { get; private set; } - private int _orderStatusId; - - private string _description; - - - // 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 - private bool _isDraft; - - // 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 IReadOnlyCollection OrderItems => _orderItems; - - private int? _paymentMethodId; - - public static Order NewDraft() - { - var order = new Order(); - order._isDraft = true; - return order; - } - - protected Order() { - _orderItems = new List(); - _isDraft = false; - } - - 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; - _orderStatusId = OrderStatus.Submitted.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, userName, 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 SetAwaitingValidationStatus() - { - if (_orderStatusId == OrderStatus.Submitted.Id) - { - AddDomainEvent(new OrderStatusChangedToAwaitingValidationDomainEvent(Id, _orderItems)); - _orderStatusId = OrderStatus.AwaitingValidation.Id; - } - } - - public void SetStockConfirmedStatus() - { - if (_orderStatusId == OrderStatus.AwaitingValidation.Id) - { - AddDomainEvent(new OrderStatusChangedToStockConfirmedDomainEvent(Id)); - - _orderStatusId = OrderStatus.StockConfirmed.Id; - _description = "All the items were confirmed with available stock."; - } - } - - public void SetPaidStatus() - { - if (_orderStatusId == OrderStatus.StockConfirmed.Id) - { - AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems)); - - _orderStatusId = OrderStatus.Paid.Id; - _description = "The payment was performed at a simulated \"American Bank checking bank account endinf on XX35071\""; - } - } - - public void SetShippedStatus() - { - if (_orderStatusId != OrderStatus.Paid.Id) - { - StatusChangeException(OrderStatus.Shipped); - } - - _orderStatusId = OrderStatus.Shipped.Id; - _description = "The order was shipped."; - AddDomainEvent(new OrderShippedDomainEvent(this)); - } - - public void SetCancelledStatus() - { - if (_orderStatusId == OrderStatus.Paid.Id || - _orderStatusId == OrderStatus.Shipped.Id) - { - StatusChangeException(OrderStatus.Cancelled); - } - - _orderStatusId = OrderStatus.Cancelled.Id; - _description = $"The order was cancelled."; - AddDomainEvent(new OrderCancelledDomainEvent(this)); - } - - public void SetCancelledStatusWhenStockIsRejected(IEnumerable orderStockRejectedItems) - { - if (_orderStatusId == OrderStatus.AwaitingValidation.Id) - { - _orderStatusId = OrderStatus.Cancelled.Id; - - var itemsStockRejectedProductNames = OrderItems - .Where(c => orderStockRejectedItems.Contains(c.ProductId)) - .Select(c => c.GetOrderItemProductName()); - - var itemsStockRejectedDescription = string.Join(", ", itemsStockRejectedProductNames); - _description = $"The product items don't have stock: ({itemsStockRejectedDescription})."; - } - } - - private void AddOrderStartedDomainEvent(string userId, string userName, int cardTypeId, string cardNumber, - string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) - { - var orderStartedDomainEvent = new OrderStartedDomainEvent(this, userId, userName, cardTypeId, - cardNumber, cardSecurityNumber, - cardHolderName, cardExpiration); - - this.AddDomainEvent(orderStartedDomainEvent); - } - - private void StatusChangeException(OrderStatus orderStatusToChange) - { - throw new OrderingDomainException($"Is not possible to change the order status from {OrderStatus.Name} to {orderStatusToChange.Name}."); - } - - public decimal GetTotal() - { - return _orderItems.Sum(o => o.GetUnits() * o.GetUnitPrice()); - } - } + 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 readonly DateTime _orderDate; + + // Address is a Value Object pattern example persisted as EF Core 2.0 owned entity + public Address Address { get; private set; } + + public int? GetBuyerId => _buyerId; + private int? _buyerId; + + public OrderStatus OrderStatus { get; private set; } + private int _orderStatusId; + + private string _description; + + + // 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 + private bool _isDraft; + + // 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 IReadOnlyCollection OrderItems => _orderItems; + + private int? _paymentMethodId; + + public static Order NewDraft() + { + var order = new Order + { + _isDraft = true + }; + return order; + } + + protected Order() + { + _orderItems = new List(); + _isDraft = false; + } + + 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; + _orderStatusId = OrderStatus.Submitted.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, userName, 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 SetAwaitingValidationStatus() + { + if (_orderStatusId == OrderStatus.Submitted.Id) + { + AddDomainEvent(new OrderStatusChangedToAwaitingValidationDomainEvent(Id, _orderItems)); + _orderStatusId = OrderStatus.AwaitingValidation.Id; + } + } + + public void SetStockConfirmedStatus() + { + if (_orderStatusId == OrderStatus.AwaitingValidation.Id) + { + AddDomainEvent(new OrderStatusChangedToStockConfirmedDomainEvent(Id)); + + _orderStatusId = OrderStatus.StockConfirmed.Id; + _description = "All the items were confirmed with available stock."; + } + } + + public void SetPaidStatus() + { + if (_orderStatusId == OrderStatus.StockConfirmed.Id) + { + AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems)); + + _orderStatusId = OrderStatus.Paid.Id; + _description = "The payment was performed at a simulated \"American Bank checking bank account endinf on XX35071\""; + } + } + + public void SetShippedStatus() + { + if (_orderStatusId != OrderStatus.Paid.Id) + { + StatusChangeException(OrderStatus.Shipped); + } + + _orderStatusId = OrderStatus.Shipped.Id; + _description = "The order was shipped."; + AddDomainEvent(new OrderShippedDomainEvent(this)); + } + + public void SetCancelledStatus() + { + if (_orderStatusId == OrderStatus.Paid.Id || + _orderStatusId == OrderStatus.Shipped.Id) + { + StatusChangeException(OrderStatus.Cancelled); + } + + _orderStatusId = OrderStatus.Cancelled.Id; + _description = $"The order was cancelled."; + AddDomainEvent(new OrderCancelledDomainEvent(this)); + } + + public void SetCancelledStatusWhenStockIsRejected(IEnumerable orderStockRejectedItems) + { + if (_orderStatusId == OrderStatus.AwaitingValidation.Id) + { + _orderStatusId = OrderStatus.Cancelled.Id; + + var itemsStockRejectedProductNames = OrderItems + .Where(c => orderStockRejectedItems.Contains(c.ProductId)) + .Select(c => c.GetOrderItemProductName()); + + var itemsStockRejectedDescription = string.Join(", ", itemsStockRejectedProductNames); + _description = $"The product items don't have stock: ({itemsStockRejectedDescription})."; + } + } + + private void AddOrderStartedDomainEvent(string userId, string userName, int cardTypeId, string cardNumber, + string cardSecurityNumber, string cardHolderName, DateTime cardExpiration) + { + var orderStartedDomainEvent = new OrderStartedDomainEvent(this, userId, userName, cardTypeId, + cardNumber, cardSecurityNumber, + cardHolderName, cardExpiration); + + AddDomainEvent(orderStartedDomainEvent); + } + + private void StatusChangeException(OrderStatus orderStatusToChange) + => throw new OrderingDomainException($"Is not possible to change the order status from {OrderStatus.Name} to {orderStatusToChange.Name}."); + + public decimal GetTotal() + => _orderItems.Sum(o => o.GetUnits() * o.GetUnitPrice()); + } } diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs index b3b7435e3..6e399b7ba 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs @@ -1,83 +1,76 @@ using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; using Ordering.Domain.Exceptions; -using System; namespace Microsoft.eShopOnContainers.Services.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) - private string _productName; - private string _pictureUrl; - private decimal _unitPrice; - private decimal _discount; - private int _units; - - public int ProductId { get; private set; } - - protected OrderItem() { } - - public OrderItem(int productId, string productName, decimal unitPrice, decimal discount, string PictureUrl, int units = 1) - { - if (units <= 0) - { - throw new OrderingDomainException("Invalid number of units"); - } - - if ((unitPrice * units) < discount) - { - throw new OrderingDomainException("The total of order item is lower than applied discount"); - } - - ProductId = productId; - - _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) - { - throw new OrderingDomainException("Discount is not valid"); - } - - _discount = discount; - } - - public void AddUnits(int units) - { - if (units < 0) - { - throw new OrderingDomainException("Invalid units"); - } - - _units += units; - } - } + 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) + private readonly string _productName; + private readonly string _pictureUrl; + private readonly decimal _unitPrice; + private decimal _discount; + private int _units; + + public int ProductId { get; private set; } + + protected OrderItem() { } + + public OrderItem(int productId, string productName, decimal unitPrice, decimal discount, string PictureUrl, int units = 1) + { + if (units <= 0) + { + throw new OrderingDomainException("Invalid number of units"); + } + + if ((unitPrice * units) < discount) + { + throw new OrderingDomainException("The total of order item is lower than applied discount"); + } + + ProductId = productId; + + _productName = productName; + _unitPrice = unitPrice; + _discount = discount; + _units = units; + _pictureUrl = PictureUrl; + } + + public string GetPictureUri() => _pictureUrl; + + public decimal GetCurrentDiscount() + => _discount; + + public int GetUnits() + => _units; + + public decimal GetUnitPrice() + => _unitPrice; + + public string GetOrderItemProductName() => _productName; + + public void SetNewDiscount(decimal discount) + { + if (discount < 0) + { + throw new OrderingDomainException("Discount is not valid"); + } + + _discount = discount; + } + + public void AddUnits(int units) + { + if (units < 0) + { + throw new OrderingDomainException("Invalid units"); + } + + _units += units; + } + } }