Moved namespaces to ordering.domain project
This commit is contained in:
parent
46dad7ac75
commit
4758530f1f
@ -1,55 +1,48 @@
|
|||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
|
||||||
using Ordering.Domain.Events;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate
|
public class Buyer
|
||||||
|
: Entity, IAggregateRoot
|
||||||
{
|
{
|
||||||
public class Buyer
|
public string IdentityGuid { get; private set; }
|
||||||
: Entity, IAggregateRoot
|
|
||||||
|
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()
|
var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration);
|
||||||
{
|
|
||||||
IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity));
|
|
||||||
Name = !string.IsNullOrWhiteSpace(name) ? name : throw new ArgumentNullException(nameof(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
public PaymentMethod VerifyOrAddPaymentMethod(
|
_paymentMethods.Add(payment);
|
||||||
int cardTypeId, string alias, string cardNumber,
|
|
||||||
string securityNumber, string cardHolderName, DateTime expiration, int orderId)
|
|
||||||
{
|
|
||||||
var existingPayment = _paymentMethods
|
|
||||||
.SingleOrDefault(p => p.IsEqualTo(cardTypeId, cardNumber, expiration));
|
|
||||||
|
|
||||||
if (existingPayment != null)
|
AddDomainEvent(new BuyerAndPaymentMethodVerifiedDomainEvent(this, payment, orderId));
|
||||||
{
|
|
||||||
AddDomainEvent(new BuyerAndPaymentMethodVerifiedDomainEvent(this, existingPayment, orderId));
|
|
||||||
|
|
||||||
return existingPayment;
|
return payment;
|
||||||
}
|
|
||||||
|
|
||||||
var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration);
|
|
||||||
|
|
||||||
_paymentMethods.Add(payment);
|
|
||||||
|
|
||||||
AddDomainEvent(new BuyerAndPaymentMethodVerifiedDomainEvent(this, payment, orderId));
|
|
||||||
|
|
||||||
return payment;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate
|
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));
|
|
||||||
|
|
||||||
public CardType(int id, string name)
|
/// <remarks>
|
||||||
: base(id, name)
|
/// 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)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
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
|
Buyer Add(Buyer buyer);
|
||||||
//as requisite for the Buyer Aggregate
|
Buyer Update(Buyer buyer);
|
||||||
|
Task<Buyer> FindAsync(string BuyerIdentityGuid);
|
||||||
public interface IBuyerRepository : IRepository<Buyer>
|
Task<Buyer> FindByIdAsync(string id);
|
||||||
{
|
|
||||||
Buyer Add(Buyer buyer);
|
|
||||||
Buyer Update(Buyer buyer);
|
|
||||||
Task<Buyer> FindAsync(string BuyerIdentityGuid);
|
|
||||||
Task<Buyer> FindByIdAsync(string id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,46 +1,41 @@
|
|||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
|
||||||
using Ordering.Domain.Exceptions;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate
|
public class PaymentMethod
|
||||||
|
: Entity
|
||||||
{
|
{
|
||||||
public class PaymentMethod
|
private string _alias;
|
||||||
: Entity
|
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;
|
_cardNumber = !string.IsNullOrWhiteSpace(cardNumber) ? cardNumber : throw new OrderingDomainException(nameof(cardNumber));
|
||||||
public CardType CardType { get; private set; }
|
_securityNumber = !string.IsNullOrWhiteSpace(securityNumber) ? securityNumber : throw new OrderingDomainException(nameof(securityNumber));
|
||||||
|
_cardHolderName = !string.IsNullOrWhiteSpace(cardHolderName) ? cardHolderName : throw new OrderingDomainException(nameof(cardHolderName));
|
||||||
|
|
||||||
|
if (expiration < DateTime.UtcNow)
|
||||||
protected PaymentMethod() { }
|
|
||||||
|
|
||||||
public PaymentMethod(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration)
|
|
||||||
{
|
{
|
||||||
|
throw new OrderingDomainException(nameof(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)
|
_alias = alias;
|
||||||
{
|
_expiration = expiration;
|
||||||
return _cardTypeId == cardTypeId
|
_cardTypeId = cardTypeId;
|
||||||
&& _cardNumber == cardNumber
|
}
|
||||||
&& _expiration == expiration;
|
|
||||||
}
|
public bool IsEqualTo(int cardTypeId, string cardNumber, DateTime expiration)
|
||||||
|
{
|
||||||
|
return _cardTypeId == cardTypeId
|
||||||
|
&& _cardNumber == cardNumber
|
||||||
|
&& _expiration == expiration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,33 @@
|
|||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
|
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; }
|
Street = street;
|
||||||
public String City { get; private set; }
|
City = city;
|
||||||
public String State { get; private set; }
|
State = state;
|
||||||
public String Country { get; private set; }
|
Country = country;
|
||||||
public String ZipCode { get; private set; }
|
ZipCode = zipcode;
|
||||||
|
}
|
||||||
|
|
||||||
public Address() { }
|
protected override IEnumerable<object> GetEqualityComponents()
|
||||||
|
{
|
||||||
public Address(string street, string city, string state, string country, string zipcode)
|
// Using a yield return statement to return each element one at a time
|
||||||
{
|
yield return Street;
|
||||||
Street = street;
|
yield return City;
|
||||||
City = city;
|
yield return State;
|
||||||
State = state;
|
yield return Country;
|
||||||
Country = country;
|
yield return ZipCode;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
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
|
Order Add(Order order);
|
||||||
//as requisite for the Order Aggregate
|
|
||||||
|
|
||||||
public interface IOrderRepository : IRepository<Order>
|
void Update(Order order);
|
||||||
{
|
|
||||||
Order Add(Order order);
|
|
||||||
|
|
||||||
void Update(Order order);
|
Task<Order> GetAsync(int orderId);
|
||||||
|
|
||||||
Task<Order> GetAsync(int orderId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,202 +1,195 @@
|
|||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
using Microsoft.eShopOnContainers.Services.Ordering.Ordering.Domain.Events;
|
||||||
using Ordering.Domain.Events;
|
|
||||||
using Ordering.Domain.Exceptions;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
|
|
||||||
|
public class Order
|
||||||
|
: Entity, IAggregateRoot
|
||||||
{
|
{
|
||||||
public class Order
|
// DDD Patterns comment
|
||||||
: Entity, IAggregateRoot
|
// 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
|
var order = new Order();
|
||||||
// Using private fields, allowed since EF Core 1.1, is a much better encapsulation
|
order._isDraft = true;
|
||||||
// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
|
return order;
|
||||||
private DateTime _orderDate;
|
}
|
||||||
|
|
||||||
// Address is a Value Object pattern example persisted as EF Core 2.0 owned entity
|
protected Order()
|
||||||
public Address Address { get; private set; }
|
{
|
||||||
|
_orderItems = new List<OrderItem>();
|
||||||
|
_isDraft = false;
|
||||||
|
}
|
||||||
|
|
||||||
public int? GetBuyerId => _buyerId;
|
public Order(string userId, string userName, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
|
||||||
private int? _buyerId;
|
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; }
|
// Add the OrderStarterDomainEvent to the domain events collection
|
||||||
private int _orderStatusId;
|
// 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();
|
||||||
|
|
||||||
|
if (existingOrderForProduct != null)
|
||||||
|
|
||||||
// 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();
|
//if previous line exist modify it with higher discount and units..
|
||||||
order._isDraft = true;
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Order()
|
if (discount > existingOrderForProduct.GetCurrentDiscount())
|
||||||
{
|
|
||||||
_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..
|
existingOrderForProduct.SetNewDiscount(discount);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_orderStatusId = OrderStatus.Shipped.Id;
|
existingOrderForProduct.AddUnits(units);
|
||||||
_description = "The order was shipped.";
|
|
||||||
AddDomainEvent(new OrderShippedDomainEvent(this));
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
public void SetCancelledStatus()
|
|
||||||
{
|
{
|
||||||
if (_orderStatusId == OrderStatus.Paid.Id ||
|
//add validated new order item
|
||||||
_orderStatusId == OrderStatus.Shipped.Id)
|
|
||||||
{
|
|
||||||
StatusChangeException(OrderStatus.Cancelled);
|
|
||||||
}
|
|
||||||
|
|
||||||
_orderStatusId = OrderStatus.Cancelled.Id;
|
var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);
|
||||||
_description = $"The order was cancelled.";
|
_orderItems.Add(orderItem);
|
||||||
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 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,82 +1,78 @@
|
|||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
using Ordering.Domain.Exceptions;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
|
public class OrderItem
|
||||||
|
: Entity
|
||||||
{
|
{
|
||||||
public class OrderItem
|
// DDD Patterns comment
|
||||||
: Entity
|
// 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
|
if (units <= 0)
|
||||||
// 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");
|
||||||
{
|
|
||||||
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;
|
if ((unitPrice * units) < discount)
|
||||||
|
|
||||||
public decimal GetCurrentDiscount()
|
|
||||||
{
|
{
|
||||||
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;
|
_units += units;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
public static OrderStatus Submitted = new OrderStatus(1, nameof(Submitted).ToLowerInvariant());
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
|
public static OrderStatus AwaitingValidation = new OrderStatus(2, nameof(AwaitingValidation).ToLowerInvariant());
|
||||||
using System;
|
public static OrderStatus StockConfirmed = new OrderStatus(3, nameof(StockConfirmed).ToLowerInvariant());
|
||||||
using System.Collections.Generic;
|
public static OrderStatus Paid = new OrderStatus(4, nameof(Paid).ToLowerInvariant());
|
||||||
using System.Linq;
|
public static OrderStatus Shipped = new OrderStatus(5, nameof(Shipped).ToLowerInvariant());
|
||||||
|
public static OrderStatus Cancelled = new OrderStatus(6, nameof(Cancelled).ToLowerInvariant());
|
||||||
|
|
||||||
public class OrderStatus
|
public OrderStatus(int id, string name)
|
||||||
: Enumeration
|
: 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)
|
public static IEnumerable<OrderStatus> List() =>
|
||||||
: base(id, name)
|
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() =>
|
return state;
|
||||||
new[] { Submitted, AwaitingValidation, StockConfirmed, Paid, Shipped, Cancelled };
|
}
|
||||||
|
|
||||||
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()
|
throw new OrderingDomainException($"Possible values for OrderStatus: {String.Join(",", List().Select(s => s.Name))}");
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OrderStatus From(int id)
|
return state;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
using MediatR;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Events;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
|
|
||||||
|
|
||||||
namespace Ordering.Domain.Events
|
public class BuyerAndPaymentMethodVerifiedDomainEvent
|
||||||
|
: INotification
|
||||||
{
|
{
|
||||||
public class BuyerAndPaymentMethodVerifiedDomainEvent
|
public Buyer Buyer { get; private set; }
|
||||||
: INotification
|
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)
|
public BuyerAndPaymentMethodVerifiedDomainEvent(Buyer buyer, PaymentMethod payment, int orderId)
|
||||||
{
|
{
|
||||||
Buyer = buyer;
|
Buyer = buyer;
|
||||||
Payment = payment;
|
Payment = payment;
|
||||||
OrderId = orderId;
|
OrderId = orderId;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
using MediatR;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Ordering.Domain.Events;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
|
||||||
|
|
||||||
namespace Ordering.Domain.Events
|
public class OrderCancelledDomainEvent : INotification
|
||||||
{
|
{
|
||||||
public class OrderCancelledDomainEvent : INotification
|
public Order Order { get; }
|
||||||
{
|
|
||||||
public Order Order { get; }
|
|
||||||
|
|
||||||
public OrderCancelledDomainEvent(Order order)
|
public OrderCancelledDomainEvent(Order order)
|
||||||
{
|
{
|
||||||
Order = order;
|
Order = order;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
using MediatR;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Events;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
|
||||||
|
|
||||||
namespace Ordering.Domain.Events
|
public class OrderShippedDomainEvent : INotification
|
||||||
{
|
{
|
||||||
public class OrderShippedDomainEvent : INotification
|
public Order Order { get; }
|
||||||
{
|
|
||||||
public Order Order { get; }
|
|
||||||
|
|
||||||
public OrderShippedDomainEvent(Order order)
|
public OrderShippedDomainEvent(Order order)
|
||||||
{
|
{
|
||||||
Order = order;
|
Order = order;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Events
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ordering.Domain.Events
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event used when an order is created
|
/// Event used when an order is created
|
||||||
|
@ -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;
|
public int OrderId { get; }
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
public IEnumerable<OrderItem> OrderItems { get; }
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
/// <summary>
|
public OrderStatusChangedToAwaitingValidationDomainEvent(int orderId,
|
||||||
/// Event used when the grace period order is confirmed
|
IEnumerable<OrderItem> orderItems)
|
||||||
/// </summary>
|
|
||||||
public class OrderStatusChangedToAwaitingValidationDomainEvent
|
|
||||||
: INotification
|
|
||||||
{
|
{
|
||||||
public int OrderId { get; }
|
OrderId = orderId;
|
||||||
public IEnumerable<OrderItem> OrderItems { get; }
|
OrderItems = orderItems;
|
||||||
|
|
||||||
public OrderStatusChangedToAwaitingValidationDomainEvent(int orderId,
|
|
||||||
IEnumerable<OrderItem> orderItems)
|
|
||||||
{
|
|
||||||
OrderId = orderId;
|
|
||||||
OrderItems = orderItems;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
public int OrderId { get; }
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
public IEnumerable<OrderItem> OrderItems { get; }
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
/// <summary>
|
public OrderStatusChangedToPaidDomainEvent(int orderId,
|
||||||
/// Event used when the order is paid
|
IEnumerable<OrderItem> orderItems)
|
||||||
/// </summary>
|
|
||||||
public class OrderStatusChangedToPaidDomainEvent
|
|
||||||
: INotification
|
|
||||||
{
|
{
|
||||||
public int OrderId { get; }
|
OrderId = orderId;
|
||||||
public IEnumerable<OrderItem> OrderItems { get; }
|
OrderItems = orderItems;
|
||||||
|
|
||||||
public OrderStatusChangedToPaidDomainEvent(int orderId,
|
|
||||||
IEnumerable<OrderItem> orderItems)
|
|
||||||
{
|
|
||||||
OrderId = orderId;
|
|
||||||
OrderItems = orderItems;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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>
|
public OrderStatusChangedToStockConfirmedDomainEvent(int orderId)
|
||||||
/// Event used when the order stock items are confirmed
|
=> OrderId = orderId;
|
||||||
/// </summary>
|
|
||||||
public class OrderStatusChangedToStockConfirmedDomainEvent
|
|
||||||
: INotification
|
|
||||||
{
|
|
||||||
public int OrderId { get; }
|
|
||||||
|
|
||||||
public OrderStatusChangedToStockConfirmedDomainEvent(int orderId)
|
|
||||||
=> OrderId = orderId;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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>
|
public OrderingDomainException()
|
||||||
/// Exception type for domain exceptions
|
{ }
|
||||||
/// </summary>
|
|
||||||
public class OrderingDomainException : Exception
|
|
||||||
{
|
|
||||||
public OrderingDomainException()
|
|
||||||
{ }
|
|
||||||
|
|
||||||
public OrderingDomainException(string message)
|
public OrderingDomainException(string message)
|
||||||
: base(message)
|
: base(message)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public OrderingDomainException(string message, Exception innerException)
|
public OrderingDomainException(string message, Exception innerException)
|
||||||
: base(message, innerException)
|
: base(message, innerException)
|
||||||
{ }
|
{ }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,92 +1,87 @@
|
|||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
||||||
|
|
||||||
|
public abstract class Entity
|
||||||
{
|
{
|
||||||
using MediatR;
|
int? _requestedHashCode;
|
||||||
using System;
|
int _Id;
|
||||||
using System.Collections.Generic;
|
public virtual int Id
|
||||||
|
|
||||||
public abstract class Entity
|
|
||||||
{
|
{
|
||||||
int? _requestedHashCode;
|
get
|
||||||
int _Id;
|
|
||||||
public virtual int Id
|
|
||||||
{
|
{
|
||||||
get
|
return _Id;
|
||||||
{
|
|
||||||
return _Id;
|
|
||||||
}
|
|
||||||
protected set
|
|
||||||
{
|
|
||||||
_Id = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
protected set
|
||||||
private List<INotification> _domainEvents;
|
|
||||||
public IReadOnlyCollection<INotification> DomainEvents => _domainEvents?.AsReadOnly();
|
|
||||||
|
|
||||||
public void AddDomainEvent(INotification eventItem)
|
|
||||||
{
|
{
|
||||||
_domainEvents = _domainEvents ?? new List<INotification>();
|
_Id = value;
|
||||||
_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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,70 +1,64 @@
|
|||||||
using System;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
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; }
|
if (obj is not Enumeration otherValue)
|
||||||
|
|
||||||
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)
|
return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeMatches = GetType().Equals(obj.GetType());
|
|
||||||
var valueMatches = Id.Equals(otherValue.Id);
|
|
||||||
|
|
||||||
return typeMatches && valueMatches;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
return typeMatches && valueMatches;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
||||||
{
|
|
||||||
|
public interface IAggregateRoot { }
|
||||||
|
|
||||||
public interface IAggregateRoot { }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
using System;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,48 +1,44 @@
|
|||||||
using System.Collections.Generic;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
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 false;
|
return ReferenceEquals(left, null) || left.Equals(right);
|
||||||
}
|
}
|
||||||
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)
|
var other = (ValueObject)obj;
|
||||||
{
|
|
||||||
return !(EqualOperator(left, right));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract IEnumerable<object> GetEqualityComponents();
|
return this.GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
|
||||||
|
}
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
if (obj == null || obj.GetType() != GetType())
|
return GetEqualityComponents()
|
||||||
{
|
.Select(x => x != null ? x.GetHashCode() : 0)
|
||||||
return false;
|
.Aggregate((x, y) => x ^ y);
|
||||||
}
|
}
|
||||||
|
|
||||||
var other = (ValueObject)obj;
|
public ValueObject GetCopy()
|
||||||
|
{
|
||||||
return this.GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
|
return this.MemberwiseClone() as ValueObject;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user