Moved namespaces to ordering.domain project

This commit is contained in:
Sumit Ghosh 2021-10-08 17:54:02 +05:30
parent 46dad7ac75
commit 4758530f1f
23 changed files with 648 additions and 740 deletions

View File

@ -1,55 +1,48 @@
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.BuyerAggregate;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate
public class Buyer
: Entity, IAggregateRoot
{
public class Buyer
: Entity, IAggregateRoot
public string IdentityGuid { get; private set; }
public string Name { get; private set; }
private List<PaymentMethod> _paymentMethods;
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();
protected Buyer()
{
public string IdentityGuid { get; private set; }
public string Name { get; private set; }
_paymentMethods = new List<PaymentMethod>();
}
private List<PaymentMethod> _paymentMethods;
public Buyer(string identity, string name) : this()
{
IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity));
Name = !string.IsNullOrWhiteSpace(name) ? name : throw new ArgumentNullException(nameof(name));
}
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();
public PaymentMethod VerifyOrAddPaymentMethod(
int cardTypeId, string alias, string cardNumber,
string securityNumber, string cardHolderName, DateTime expiration, int orderId)
{
var existingPayment = _paymentMethods
.SingleOrDefault(p => p.IsEqualTo(cardTypeId, cardNumber, expiration));
protected Buyer()
if (existingPayment != null)
{
AddDomainEvent(new BuyerAndPaymentMethodVerifiedDomainEvent(this, existingPayment, orderId));
_paymentMethods = new List<PaymentMethod>();
return existingPayment;
}
public Buyer(string identity, string name) : this()
{
IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity));
Name = !string.IsNullOrWhiteSpace(name) ? name : throw new ArgumentNullException(nameof(name));
}
var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration);
public PaymentMethod VerifyOrAddPaymentMethod(
int cardTypeId, string alias, string cardNumber,
string securityNumber, string cardHolderName, DateTime expiration, int orderId)
{
var existingPayment = _paymentMethods
.SingleOrDefault(p => p.IsEqualTo(cardTypeId, cardNumber, expiration));
_paymentMethods.Add(payment);
if (existingPayment != null)
{
AddDomainEvent(new BuyerAndPaymentMethodVerifiedDomainEvent(this, existingPayment, orderId));
AddDomainEvent(new BuyerAndPaymentMethodVerifiedDomainEvent(this, payment, orderId));
return existingPayment;
}
var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration);
_paymentMethods.Add(payment);
AddDomainEvent(new BuyerAndPaymentMethodVerifiedDomainEvent(this, payment, orderId));
return payment;
}
return payment;
}
}

View File

@ -1,21 +1,20 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate
{
/// <remarks>
/// Card type class should be marked as abstract with protected constructor to encapsulate known enum types
/// this is currently not possible as OrderingContextSeed uses this constructor to load cardTypes from csv file
/// </remarks>
public class CardType
: Enumeration
{
public static CardType Amex = new(1, nameof(Amex));
public static CardType Visa = new(2, nameof(Visa));
public static CardType MasterCard = new(3, nameof(MasterCard));
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
public CardType(int id, string name)
: base(id, name)
{
}
/// <remarks>
/// Card type class should be marked as abstract with protected constructor to encapsulate known enum types
/// this is currently not possible as OrderingContextSeed uses this constructor to load cardTypes from csv file
/// </remarks>
public class CardType
: Enumeration
{
public static CardType Amex = new(1, nameof(Amex));
public static CardType Visa = new(2, nameof(Visa));
public static CardType MasterCard = new(3, nameof(MasterCard));
public CardType(int id, string name)
: base(id, name)
{
}
}

View File

@ -1,16 +1,13 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate
//This is just the RepositoryContracts or Interface defined at the Domain Layer
//as requisite for the Buyer Aggregate
public interface IBuyerRepository : IRepository<Buyer>
{
//This is just the RepositoryContracts or Interface defined at the Domain Layer
//as requisite for the Buyer Aggregate
public interface IBuyerRepository : IRepository<Buyer>
{
Buyer Add(Buyer buyer);
Buyer Update(Buyer buyer);
Task<Buyer> FindAsync(string BuyerIdentityGuid);
Task<Buyer> FindByIdAsync(string id);
}
Buyer Add(Buyer buyer);
Buyer Update(Buyer buyer);
Task<Buyer> FindAsync(string BuyerIdentityGuid);
Task<Buyer> FindByIdAsync(string id);
}

View File

@ -1,46 +1,41 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using Ordering.Domain.Exceptions;
using System;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate
public class PaymentMethod
: Entity
{
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)
{
private string _alias;
private string _cardNumber;
private string _securityNumber;
private string _cardHolderName;
private DateTime _expiration;
private int _cardTypeId;
public CardType CardType { get; private set; }
_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));
protected PaymentMethod() { }
public PaymentMethod(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration)
if (expiration < DateTime.UtcNow)
{
_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;
throw new OrderingDomainException(nameof(expiration));
}
public bool IsEqualTo(int cardTypeId, string cardNumber, DateTime expiration)
{
return _cardTypeId == cardTypeId
&& _cardNumber == cardNumber
&& _expiration == expiration;
}
_alias = alias;
_expiration = expiration;
_cardTypeId = cardTypeId;
}
public bool IsEqualTo(int cardTypeId, string cardNumber, DateTime expiration)
{
return _cardTypeId == cardTypeId
&& _cardNumber == cardNumber
&& _expiration == expiration;
}
}

View File

@ -1,36 +1,33 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
using System;
using System.Collections.Generic;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
public class Address : ValueObject
{
public class Address : ValueObject
public String Street { get; private set; }
public String City { get; private set; }
public String State { get; private set; }
public String Country { get; private set; }
public String ZipCode { get; private set; }
public Address() { }
public Address(string street, string city, string state, string country, string zipcode)
{
public String Street { get; private set; }
public String City { get; private set; }
public String State { get; private set; }
public String Country { get; private set; }
public String ZipCode { get; private set; }
Street = street;
City = city;
State = state;
Country = country;
ZipCode = zipcode;
}
public Address() { }
public Address(string street, string city, string state, string country, string zipcode)
{
Street = street;
City = city;
State = state;
Country = country;
ZipCode = zipcode;
}
protected override IEnumerable<object> GetEqualityComponents()
{
// Using a yield return statement to return each element one at a time
yield return Street;
yield return City;
yield return State;
yield return Country;
yield return ZipCode;
}
protected override IEnumerable<object> GetEqualityComponents()
{
// Using a yield return statement to return each element one at a time
yield return Street;
yield return City;
yield return State;
yield return Country;
yield return ZipCode;
}
}

View File

@ -1,17 +1,13 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
//This is just the RepositoryContracts or Interface defined at the Domain Layer
//as requisite for the Order Aggregate
public interface IOrderRepository : IRepository<Order>
{
//This is just the RepositoryContracts or Interface defined at the Domain Layer
//as requisite for the Order Aggregate
Order Add(Order order);
public interface IOrderRepository : IRepository<Order>
{
Order Add(Order order);
void Update(Order order);
void Update(Order order);
Task<Order> GetAsync(int orderId);
}
Task<Order> GetAsync(int orderId);
}

View File

@ -1,202 +1,195 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using Ordering.Domain.Events;
using Ordering.Domain.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.eShopOnContainers.Services.Ordering.Ordering.Domain.Events;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
public class Order
: Entity, IAggregateRoot
{
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()
{
// 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;
var order = new Order();
order._isDraft = true;
return order;
}
// Address is a Value Object pattern example persisted as EF Core 2.0 owned entity
public Address Address { get; private set; }
protected Order()
{
_orderItems = new List<OrderItem>();
_isDraft = false;
}
public int? GetBuyerId => _buyerId;
private int? _buyerId;
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;
public OrderStatus OrderStatus { get; private set; }
private int _orderStatusId;
// 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);
}
private string _description;
// 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();
// 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()
if (existingOrderForProduct != null)
{
var order = new Order();
order._isDraft = true;
return order;
}
//if previous line exist modify it with higher discount and units..
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 (discount > existingOrderForProduct.GetCurrentDiscount())
{
//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 ending on XX35071\"";
}
}
public void SetShippedStatus()
{
if (_orderStatusId != OrderStatus.Paid.Id)
{
StatusChangeException(OrderStatus.Shipped);
existingOrderForProduct.SetNewDiscount(discount);
}
_orderStatusId = OrderStatus.Shipped.Id;
_description = "The order was shipped.";
AddDomainEvent(new OrderShippedDomainEvent(this));
existingOrderForProduct.AddUnits(units);
}
public void SetCancelledStatus()
else
{
if (_orderStatusId == OrderStatus.Paid.Id ||
_orderStatusId == OrderStatus.Shipped.Id)
{
StatusChangeException(OrderStatus.Cancelled);
}
//add validated new order item
_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());
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 ending 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());
}
}

View File

@ -1,82 +1,78 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using Ordering.Domain.Exceptions;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
public class OrderItem
: Entity
{
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)
{
// 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)
{
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;
throw new OrderingDomainException("Invalid number of units");
}
public string GetPictureUri() => _pictureUrl;
public decimal GetCurrentDiscount()
if ((unitPrice * units) < discount)
{
return _discount;
throw new OrderingDomainException("The total of order item is lower than applied discount");
}
public int GetUnits()
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)
{
return _units;
throw new OrderingDomainException("Discount is not valid");
}
public decimal GetUnitPrice()
_discount = discount;
}
public void AddUnits(int units)
{
if (units < 0)
{
return _unitPrice;
throw new OrderingDomainException("Invalid units");
}
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;
}
_units += units;
}
}

View File

@ -1,52 +1,47 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
public class OrderStatus
: Enumeration
{
using global::Ordering.Domain.Exceptions;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
using System;
using System.Collections.Generic;
using System.Linq;
public static OrderStatus Submitted = new OrderStatus(1, nameof(Submitted).ToLowerInvariant());
public static OrderStatus AwaitingValidation = new OrderStatus(2, nameof(AwaitingValidation).ToLowerInvariant());
public static OrderStatus StockConfirmed = new OrderStatus(3, nameof(StockConfirmed).ToLowerInvariant());
public static OrderStatus Paid = new OrderStatus(4, nameof(Paid).ToLowerInvariant());
public static OrderStatus Shipped = new OrderStatus(5, nameof(Shipped).ToLowerInvariant());
public static OrderStatus Cancelled = new OrderStatus(6, nameof(Cancelled).ToLowerInvariant());
public class OrderStatus
: Enumeration
public OrderStatus(int id, string name)
: base(id, name)
{
public static OrderStatus Submitted = new OrderStatus(1, nameof(Submitted).ToLowerInvariant());
public static OrderStatus AwaitingValidation = new OrderStatus(2, nameof(AwaitingValidation).ToLowerInvariant());
public static OrderStatus StockConfirmed = new OrderStatus(3, nameof(StockConfirmed).ToLowerInvariant());
public static OrderStatus Paid = new OrderStatus(4, nameof(Paid).ToLowerInvariant());
public static OrderStatus Shipped = new OrderStatus(5, nameof(Shipped).ToLowerInvariant());
public static OrderStatus Cancelled = new OrderStatus(6, nameof(Cancelled).ToLowerInvariant());
}
public OrderStatus(int id, string name)
: base(id, name)
public static IEnumerable<OrderStatus> List() =>
new[] { Submitted, AwaitingValidation, StockConfirmed, Paid, Shipped, Cancelled };
public static OrderStatus FromName(string name)
{
var state = List()
.SingleOrDefault(s => String.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (state == null)
{
throw new OrderingDomainException($"Possible values for OrderStatus: {String.Join(",", List().Select(s => s.Name))}");
}
public static IEnumerable<OrderStatus> List() =>
new[] { Submitted, AwaitingValidation, StockConfirmed, Paid, Shipped, Cancelled };
return state;
}
public static OrderStatus FromName(string name)
public static OrderStatus From(int id)
{
var state = List().SingleOrDefault(s => s.Id == id);
if (state == null)
{
var state = List()
.SingleOrDefault(s => String.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (state == null)
{
throw new OrderingDomainException($"Possible values for OrderStatus: {String.Join(",", List().Select(s => s.Name))}");
}
return state;
throw new OrderingDomainException($"Possible values for OrderStatus: {String.Join(",", List().Select(s => s.Name))}");
}
public static OrderStatus From(int id)
{
var state = List().SingleOrDefault(s => s.Id == id);
if (state == null)
{
throw new OrderingDomainException($"Possible values for OrderStatus: {String.Join(",", List().Select(s => s.Name))}");
}
return state;
}
return state;
}
}

View File

@ -1,20 +1,16 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Events;
namespace Ordering.Domain.Events
public class BuyerAndPaymentMethodVerifiedDomainEvent
: INotification
{
public class BuyerAndPaymentMethodVerifiedDomainEvent
: INotification
{
public Buyer Buyer { get; private set; }
public PaymentMethod Payment { get; private set; }
public int OrderId { get; private set; }
public Buyer Buyer { get; private set; }
public PaymentMethod Payment { get; private set; }
public int OrderId { get; private set; }
public BuyerAndPaymentMethodVerifiedDomainEvent(Buyer buyer, PaymentMethod payment, int orderId)
{
Buyer = buyer;
Payment = payment;
OrderId = orderId;
}
public BuyerAndPaymentMethodVerifiedDomainEvent(Buyer buyer, PaymentMethod payment, int orderId)
{
Buyer = buyer;
Payment = payment;
OrderId = orderId;
}
}

View File

@ -1,15 +1,12 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
namespace Microsoft.eShopOnContainers.Services.Ordering.Ordering.Domain.Events;
namespace Ordering.Domain.Events
public class OrderCancelledDomainEvent : INotification
{
public class OrderCancelledDomainEvent : INotification
{
public Order Order { get; }
public Order Order { get; }
public OrderCancelledDomainEvent(Order order)
{
Order = order;
}
public OrderCancelledDomainEvent(Order order)
{
Order = order;
}
}

View File

@ -1,15 +1,11 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Events;
namespace Ordering.Domain.Events
public class OrderShippedDomainEvent : INotification
{
public class OrderShippedDomainEvent : INotification
{
public Order Order { get; }
public Order Order { get; }
public OrderShippedDomainEvent(Order order)
{
Order = order;
}
public OrderShippedDomainEvent(Order order)
{
Order = order;
}
}

View File

@ -1,8 +1,5 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using System;
namespace Ordering.Domain.Events

namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Events
{
/// <summary>
/// Event used when an order is created

View File

@ -1,23 +1,18 @@
namespace Ordering.Domain.Events
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Events;
/// <summary>
/// Event used when the grace period order is confirmed
/// </summary>
public class OrderStatusChangedToAwaitingValidationDomainEvent
: INotification
{
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using System.Collections.Generic;
public int OrderId { get; }
public IEnumerable<OrderItem> OrderItems { get; }
/// <summary>
/// Event used when the grace period order is confirmed
/// </summary>
public class OrderStatusChangedToAwaitingValidationDomainEvent
: INotification
public OrderStatusChangedToAwaitingValidationDomainEvent(int orderId,
IEnumerable<OrderItem> orderItems)
{
public int OrderId { get; }
public IEnumerable<OrderItem> OrderItems { get; }
public OrderStatusChangedToAwaitingValidationDomainEvent(int orderId,
IEnumerable<OrderItem> orderItems)
{
OrderId = orderId;
OrderItems = orderItems;
}
OrderId = orderId;
OrderItems = orderItems;
}
}
}

View File

@ -1,23 +1,17 @@
namespace Ordering.Domain.Events
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Events;
/// <summary>
/// Event used when the order is paid
/// </summary>
public class OrderStatusChangedToPaidDomainEvent
: INotification
{
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using System.Collections.Generic;
public int OrderId { get; }
public IEnumerable<OrderItem> OrderItems { get; }
/// <summary>
/// Event used when the order is paid
/// </summary>
public class OrderStatusChangedToPaidDomainEvent
: INotification
public OrderStatusChangedToPaidDomainEvent(int orderId,
IEnumerable<OrderItem> orderItems)
{
public int OrderId { get; }
public IEnumerable<OrderItem> OrderItems { get; }
public OrderStatusChangedToPaidDomainEvent(int orderId,
IEnumerable<OrderItem> orderItems)
{
OrderId = orderId;
OrderItems = orderItems;
}
OrderId = orderId;
OrderItems = orderItems;
}
}
}

View File

@ -1,16 +1,13 @@
namespace Ordering.Domain.Events
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Events;
/// <summary>
/// Event used when the order stock items are confirmed
/// </summary>
public class OrderStatusChangedToStockConfirmedDomainEvent
: INotification
{
using MediatR;
public int OrderId { get; }
/// <summary>
/// Event used when the order stock items are confirmed
/// </summary>
public class OrderStatusChangedToStockConfirmedDomainEvent
: INotification
{
public int OrderId { get; }
public OrderStatusChangedToStockConfirmedDomainEvent(int orderId)
=> OrderId = orderId;
}
}
public OrderStatusChangedToStockConfirmedDomainEvent(int orderId)
=> OrderId = orderId;
}

View File

@ -1,21 +1,18 @@
using System;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Exceptions;
namespace Ordering.Domain.Exceptions
/// <summary>
/// Exception type for domain exceptions
/// </summary>
public class OrderingDomainException : Exception
{
/// <summary>
/// Exception type for domain exceptions
/// </summary>
public class OrderingDomainException : Exception
{
public OrderingDomainException()
{ }
public OrderingDomainException()
{ }
public OrderingDomainException(string message)
: base(message)
{ }
public OrderingDomainException(string message)
: base(message)
{ }
public OrderingDomainException(string message, Exception innerException)
: base(message, innerException)
{ }
}
public OrderingDomainException(string message, Exception innerException)
: base(message, innerException)
{ }
}

View File

@ -1,92 +1,87 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
public abstract class Entity
{
using MediatR;
using System;
using System.Collections.Generic;
public abstract class Entity
int? _requestedHashCode;
int _Id;
public virtual int Id
{
int? _requestedHashCode;
int _Id;
public virtual int Id
get
{
get
{
return _Id;
}
protected set
{
_Id = value;
}
return _Id;
}
private List<INotification> _domainEvents;
public IReadOnlyCollection<INotification> DomainEvents => _domainEvents?.AsReadOnly();
public void AddDomainEvent(INotification eventItem)
protected set
{
_domainEvents = _domainEvents ?? new List<INotification>();
_domainEvents.Add(eventItem);
}
public void RemoveDomainEvent(INotification eventItem)
{
_domainEvents?.Remove(eventItem);
}
public void ClearDomainEvents()
{
_domainEvents?.Clear();
}
public bool IsTransient()
{
return this.Id == default(Int32);
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is Entity))
return false;
if (Object.ReferenceEquals(this, obj))
return true;
if (this.GetType() != obj.GetType())
return false;
Entity item = (Entity)obj;
if (item.IsTransient() || this.IsTransient())
return false;
else
return item.Id == this.Id;
}
public override int GetHashCode()
{
if (!IsTransient())
{
if (!_requestedHashCode.HasValue)
_requestedHashCode = this.Id.GetHashCode() ^ 31; // XOR for random distribution (http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx)
return _requestedHashCode.Value;
}
else
return base.GetHashCode();
}
public static bool operator ==(Entity left, Entity right)
{
if (Object.Equals(left, null))
return (Object.Equals(right, null)) ? true : false;
else
return left.Equals(right);
}
public static bool operator !=(Entity left, Entity right)
{
return !(left == right);
_Id = value;
}
}
private List<INotification> _domainEvents;
public IReadOnlyCollection<INotification> DomainEvents => _domainEvents?.AsReadOnly();
public void AddDomainEvent(INotification eventItem)
{
_domainEvents = _domainEvents ?? new List<INotification>();
_domainEvents.Add(eventItem);
}
public void RemoveDomainEvent(INotification eventItem)
{
_domainEvents?.Remove(eventItem);
}
public void ClearDomainEvents()
{
_domainEvents?.Clear();
}
public bool IsTransient()
{
return this.Id == default(Int32);
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is Entity))
return false;
if (Object.ReferenceEquals(this, obj))
return true;
if (this.GetType() != obj.GetType())
return false;
Entity item = (Entity)obj;
if (item.IsTransient() || this.IsTransient())
return false;
else
return item.Id == this.Id;
}
public override int GetHashCode()
{
if (!IsTransient())
{
if (!_requestedHashCode.HasValue)
_requestedHashCode = this.Id.GetHashCode() ^ 31; // XOR for random distribution (http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx)
return _requestedHashCode.Value;
}
else
return base.GetHashCode();
}
public static bool operator ==(Entity left, Entity right)
{
if (Object.Equals(left, null))
return (Object.Equals(right, null)) ? true : false;
else
return left.Equals(right);
}
public static bool operator !=(Entity left, Entity right)
{
return !(left == right);
}
}

View File

@ -1,70 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork
public abstract class Enumeration : IComparable
{
public abstract class Enumeration : IComparable
public string Name { get; private set; }
public int Id { get; private set; }
protected Enumeration(int id, string name) => (Id, Name) = (id, name);
public override string ToString() => Name;
public static IEnumerable<T> GetAll<T>() where T : Enumeration =>
typeof(T).GetFields(BindingFlags.Public |
BindingFlags.Static |
BindingFlags.DeclaredOnly)
.Select(f => f.GetValue(null))
.Cast<T>();
public override bool Equals(object obj)
{
public string Name { get; private set; }
public int Id { get; private set; }
protected Enumeration(int id, string name) => (Id, Name) = (id, name);
public override string ToString() => Name;
public static IEnumerable<T> GetAll<T>() where T : Enumeration =>
typeof(T).GetFields(BindingFlags.Public |
BindingFlags.Static |
BindingFlags.DeclaredOnly)
.Select(f => f.GetValue(null))
.Cast<T>();
public override bool Equals(object obj)
if (obj is not Enumeration otherValue)
{
if (obj is not Enumeration otherValue)
{
return false;
}
var typeMatches = GetType().Equals(obj.GetType());
var valueMatches = Id.Equals(otherValue.Id);
return typeMatches && valueMatches;
return false;
}
public override int GetHashCode() => Id.GetHashCode();
var typeMatches = GetType().Equals(obj.GetType());
var valueMatches = Id.Equals(otherValue.Id);
public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)
{
var absoluteDifference = Math.Abs(firstValue.Id - secondValue.Id);
return absoluteDifference;
}
public static T FromValue<T>(int value) where T : Enumeration
{
var matchingItem = Parse<T, int>(value, "value", item => item.Id == value);
return matchingItem;
}
public static T FromDisplayName<T>(string displayName) where T : Enumeration
{
var matchingItem = Parse<T, string>(displayName, "display name", item => item.Name == displayName);
return matchingItem;
}
private static T Parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration
{
var matchingItem = GetAll<T>().FirstOrDefault(predicate);
if (matchingItem == null)
throw new InvalidOperationException($"'{value}' is not a valid {description} in {typeof(T)}");
return matchingItem;
}
public int CompareTo(object other) => Id.CompareTo(((Enumeration)other).Id);
return typeMatches && valueMatches;
}
public override int GetHashCode() => Id.GetHashCode();
public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)
{
var absoluteDifference = Math.Abs(firstValue.Id - secondValue.Id);
return absoluteDifference;
}
public static T FromValue<T>(int value) where T : Enumeration
{
var matchingItem = Parse<T, int>(value, "value", item => item.Id == value);
return matchingItem;
}
public static T FromDisplayName<T>(string displayName) where T : Enumeration
{
var matchingItem = Parse<T, string>(displayName, "display name", item => item.Name == displayName);
return matchingItem;
}
private static T Parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration
{
var matchingItem = GetAll<T>().FirstOrDefault(predicate);
if (matchingItem == null)
throw new InvalidOperationException($"'{value}' is not a valid {description} in {typeof(T)}");
return matchingItem;
}
public int CompareTo(object other) => Id.CompareTo(((Enumeration)other).Id);
}

View File

@ -1,6 +1,5 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork
{
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
public interface IAggregateRoot { }
public interface IAggregateRoot { }
}

View File

@ -1,7 +1,6 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
public interface IRepository<T> where T : IAggregateRoot
{
public interface IRepository<T> where T : IAggregateRoot
{
IUnitOfWork UnitOfWork { get; }
}
IUnitOfWork UnitOfWork { get; }
}

View File

@ -1,12 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork
public interface IUnitOfWork : IDisposable
{
public interface IUnitOfWork : IDisposable
{
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken));
}
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken));
}

View File

@ -1,48 +1,44 @@
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork
public abstract class ValueObject
{
public abstract class ValueObject
protected static bool EqualOperator(ValueObject left, ValueObject right)
{
protected static bool EqualOperator(ValueObject left, ValueObject right)
if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null))
{
if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null))
{
return false;
}
return ReferenceEquals(left, null) || left.Equals(right);
return false;
}
return ReferenceEquals(left, null) || left.Equals(right);
}
protected static bool NotEqualOperator(ValueObject left, ValueObject right)
{
return !(EqualOperator(left, right));
}
protected abstract IEnumerable<object> GetEqualityComponents();
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
{
return false;
}
protected static bool NotEqualOperator(ValueObject left, ValueObject right)
{
return !(EqualOperator(left, right));
}
var other = (ValueObject)obj;
protected abstract IEnumerable<object> GetEqualityComponents();
return this.GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
}
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
{
return false;
}
public override int GetHashCode()
{
return GetEqualityComponents()
.Select(x => x != null ? x.GetHashCode() : 0)
.Aggregate((x, y) => x ^ y);
}
var other = (ValueObject)obj;
return this.GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
}
public override int GetHashCode()
{
return GetEqualityComponents()
.Select(x => x != null ? x.GetHashCode() : 0)
.Aggregate((x, y) => x ^ y);
}
public ValueObject GetCopy()
{
return this.MemberwiseClone() as ValueObject;
}
public ValueObject GetCopy()
{
return this.MemberwiseClone() as ValueObject;
}
}