Browse Source

make fields readonly

pull/986/head
Rafsanul Hasan 5 years ago
parent
commit
99d47401c0
No known key found for this signature in database GPG Key ID: 4BBF45E04D0AD72B
3 changed files with 291 additions and 305 deletions
  1. +37
    -39
      src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs
  2. +184
    -189
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
  3. +70
    -77
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs

+ 37
- 39
src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs View File

@ -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;
}
}

+ 184
- 189
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs View File

@ -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<OrderItem> _orderItems;
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
private int? _paymentMethodId;
public static Order NewDraft()
{
var order = new Order();
order._isDraft = true;
return order;
}
protected Order() {
_orderItems = new List<OrderItem>();
_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<int> 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<OrderItem> _orderItems;
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
private int? _paymentMethodId;
public static Order NewDraft()
{
var order = new Order
{
_isDraft = true
};
return order;
}
protected Order()
{
_orderItems = new List<OrderItem>();
_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<int> 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());
}
}

+ 70
- 77
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs View File

@ -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;
}
}
}

Loading…
Cancel
Save