@ -0,0 +1,11 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.API.DTO | |||
{ | |||
public class OrderDTO | |||
{ | |||
} | |||
} |
@ -0,0 +1,26 @@ | |||
using System; | |||
using System.Runtime.Serialization; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Order | |||
{ | |||
[DataContract] | |||
public sealed class OrderItemDTO | |||
{ | |||
public OrderItemDTO() { } | |||
[DataMember] | |||
public Guid Id { get; set; } | |||
[DataMember] | |||
public int Quantity { get; set; } | |||
[DataMember] | |||
public int FulfillmentRemaining { get; set; } | |||
public override string ToString() | |||
{ | |||
return String.Format("ID: {0}, Quantity: {1}, Fulfillment Remaing: {2}", this.ItemId, this.Quantity, this.FulfillmentRemaining); | |||
} | |||
} | |||
} |
@ -0,0 +1,56 @@ | |||
using System; | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel | |||
{ | |||
public class Buyer : AggregateRoot | |||
{ | |||
public Buyer(Guid buyerId, string name, string lastName, string email, Address address, string phoneNumber) | |||
{ | |||
this.BuyerId = buyerId; | |||
this.Name = name; | |||
this.LastName = lastName; | |||
this.Email = email; | |||
this.Address = address; | |||
this.PhoneNumber = phoneNumber; | |||
} | |||
protected Buyer() { } // infrastructure | |||
public virtual Guid BuyerId | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual string Name | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual string LastName | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual string Email | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual Address Address | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual string PhoneNumber | |||
{ | |||
get; | |||
private set; | |||
} | |||
} | |||
} |
@ -0,0 +1,64 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel | |||
{ | |||
public class Address : ValueObject<Address> //A VO doesn't have IDENTITY, like in this case, the Address | |||
{ | |||
public virtual String Street | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual String City | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual String State | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual String StateCode | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual String Country | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual String CountryCode | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual String ZipCode | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual double Latitude | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual double Longitude | |||
{ | |||
get; | |||
private set; | |||
} | |||
} | |||
} |
@ -0,0 +1,77 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel | |||
{ | |||
public class Order : AggregateRoot | |||
{ | |||
public Order(Guid buyerId, Address shippingAddress, Address billingAddress) | |||
: this(buyerId, shippingAddress, billingAddress, DateTime.UtcNow) | |||
{ | |||
} | |||
public Order(Guid buyerId, Address shippingAddress, Address billingAddress, DateTime orderDate) | |||
{ | |||
this.BuyerId = buyerId; | |||
this.ShippingAddress = shippingAddress; | |||
this.BillingAddress = billingAddress; | |||
this.OrderDate = orderDate; | |||
} | |||
protected Order() { } // Infrastructure. EF might need a plain constructor. Do not use. | |||
//Order ID comes derived from the Entity base class | |||
List<OrderItem> _orderItems; | |||
public virtual List<OrderItem> orderItems | |||
{ | |||
get | |||
{ | |||
if (_orderItems == null) | |||
_orderItems = new List<OrderItem>(); | |||
return _orderItems; | |||
} | |||
private set | |||
{ | |||
_orderItems = value; | |||
} | |||
} | |||
public virtual Guid BuyerId | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual Address ShippingAddress | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual Address BillingAddress | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual DateTime OrderDate | |||
{ | |||
get; | |||
private set; | |||
} | |||
public virtual OrderStatus Status | |||
{ | |||
get; | |||
set; | |||
} | |||
} | |||
} |
@ -0,0 +1,35 @@ | |||
using System; | |||
using System.Runtime.Serialization; | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel | |||
{ | |||
public class OrderItem : Entity | |||
{ | |||
//(CDLTLL) Might remove this constructor so | |||
// just with a method in the Order-AggregateRoot you can add an OrderItem.. | |||
//public OrderItem(Guid itemId, decimal itemPrice, int quantity) | |||
//{ | |||
// this.Id = itemId; | |||
// this.ItemPrice = itemPrice; | |||
// this.Quantity = quantity; | |||
// this.FulfillmentRemaining = quantity; <---- Put this logic into the AggregateRoot method AddOrderItem() | |||
//} | |||
protected OrderItem() { } // Infrastructure. EF might need a plain constructor. Do not use. | |||
public Guid ItemId { get; set; } | |||
public decimal ItemPrice { get; set; } | |||
public int Quantity { get; set; } | |||
public int FulfillmentRemaining { get; set; } | |||
public override string ToString() | |||
{ | |||
return String.Format("ID: {0}, Quantity: {1}, Fulfillment Remaing: {2}", this.ItemId, this.Quantity, this.FulfillmentRemaining); | |||
} | |||
} | |||
} |
@ -0,0 +1,18 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel | |||
{ | |||
public enum OrderStatus | |||
{ | |||
Unknown, | |||
New, | |||
Submitted, | |||
InProcess, | |||
Backordered, | |||
Shipped, | |||
Canceled, | |||
} | |||
} |
@ -0,0 +1,11 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts | |||
{ | |||
interface IBuyerRepository | |||
{ | |||
} | |||
} |
@ -0,0 +1,17 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; | |||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts | |||
{ | |||
public interface IOrderRepository : IRepository<Order> | |||
{ | |||
Order Get(int associatedConferenceId); | |||
void ModifyOrder(Order seatOrder); | |||
} | |||
} |
@ -1,11 +1,130 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork | |||
{ | |||
public class Entity | |||
/// <summary> | |||
/// Base class for entities | |||
/// </summary> | |||
public abstract class Entity | |||
{ | |||
//Members | |||
int? _requestedHashCode; | |||
Guid _Id; | |||
//Properties | |||
/// <summary> | |||
/// Get the persisten object identifier | |||
/// </summary> | |||
public virtual Guid Id | |||
{ | |||
get | |||
{ | |||
return _Id; | |||
} | |||
protected set | |||
{ | |||
_Id = value; | |||
} | |||
} | |||
//Public Methods | |||
/// <summary> | |||
/// Check if this entity is transient, ie, without identity at this moment | |||
/// </summary> | |||
/// <returns>True if entity is transient, else false</returns> | |||
public bool IsTransient() | |||
{ | |||
return this.Id == Guid.Empty; | |||
} | |||
/// <summary> | |||
/// Generate identity for this entity | |||
/// </summary> | |||
public void GenerateNewIdentity() | |||
{ | |||
if ( IsTransient()) | |||
this.Id = IdentityGenerator.NewSequentialGuid(); | |||
} | |||
/// <summary> | |||
/// Change current identity for a new non transient identity | |||
/// </summary> | |||
/// <param name="identity">the new identity</param> | |||
public void ChangeCurrentIdentity(Guid identity) | |||
{ | |||
if ( identity != Guid.Empty) | |||
this.Id = identity; | |||
} | |||
//Overrides Methods | |||
/// <summary> | |||
/// <see cref="M:System.Object.Equals"/> | |||
/// </summary> | |||
/// <param name="obj"><see cref="M:System.Object.Equals"/></param> | |||
/// <returns><see cref="M:System.Object.Equals"/></returns> | |||
public override bool Equals(object obj) | |||
{ | |||
if (obj == null || !(obj is Entity)) | |||
return false; | |||
if (Object.ReferenceEquals(this, obj)) | |||
return true; | |||
Entity item = (Entity)obj; | |||
if (item.IsTransient() || this.IsTransient()) | |||
return false; | |||
else | |||
return item.Id == this.Id; | |||
} | |||
/// <summary> | |||
/// <see cref="M:System.Object.GetHashCode"/> | |||
/// </summary> | |||
/// <returns><see cref="M:System.Object.GetHashCode"/></returns> | |||
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(); | |||
} | |||
/// <summary> | |||
/// equals operator | |||
/// </summary> | |||
/// <param name="left">left parameter</param> | |||
/// <param name="right">right parameter</param> | |||
/// <returns>true if equals</returns> | |||
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); | |||
} | |||
/// <summary> | |||
/// Distinct operator | |||
/// </summary> | |||
/// <param name="left">left parameter</param> | |||
/// <param name="right">right parameter</param> | |||
/// <returns>True if different</returns> | |||
public static bool operator !=(Entity left, Entity right) | |||
{ | |||
return !(left == right); | |||
} | |||
} | |||
} |
@ -0,0 +1,93 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Linq.Expressions; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork | |||
{ | |||
/// <summary> | |||
/// Base interface for implement a "Repository Pattern", for | |||
/// more information about this pattern see http://martinfowler.com/eaaCatalog/repository.html | |||
/// or http://blogs.msdn.com/adonet/archive/2009/06/16/using-repository-and-unit-of-work-patterns-with-entity-framework-4-0.aspx | |||
/// </summary> | |||
/// <remarks> | |||
/// Indeed, DbSet is already a generic repository and therefore you would not need a Repo. | |||
/// But using this Repo interface allows us to ensure PI (Persistence Ignorant) principle | |||
/// within our domain model | |||
/// </remarks> | |||
/// <typeparam name="TEntity">Type of entity for this repository </typeparam> | |||
public interface IRepository<TEntity> : IDisposable | |||
where TEntity : Entity | |||
{ | |||
/// <summary> | |||
/// Get the unit of work in this repository | |||
/// </summary> | |||
IUnitOfWork UnitOfWork { get; } | |||
/// <summary> | |||
/// Add item into repository | |||
/// </summary> | |||
/// <param name="item">Item to add to repository</param> | |||
void Add(TEntity item); | |||
/// <summary> | |||
/// Delete item | |||
/// </summary> | |||
/// <param name="item">Item to delete</param> | |||
void Remove(TEntity item); | |||
/// <summary> | |||
/// Set item as modified | |||
/// </summary> | |||
/// <param name="item">Item to modify</param> | |||
void Modify(TEntity item); | |||
/// <summary> | |||
///Track entity into this repository, really in UnitOfWork. | |||
///In EF this can be done with Attach and with Update in NH | |||
/// </summary> | |||
/// <param name="item">Item to attach</param> | |||
void TrackItem(TEntity item); | |||
/// <summary> | |||
/// Sets modified entity into the repository. | |||
/// When calling Commit() method in UnitOfWork | |||
/// these changes will be saved into the storage | |||
/// </summary> | |||
/// <param name="persisted">The persisted item</param> | |||
/// <param name="current">The current item</param> | |||
void Merge(TEntity persisted, TEntity current); | |||
/// <summary> | |||
/// Get element by entity key | |||
/// </summary> | |||
/// <param name="id">Entity key value</param> | |||
/// <returns></returns> | |||
TEntity Get(Guid id); | |||
/// <summary> | |||
/// Get all elements of type TEntity in repository | |||
/// </summary> | |||
/// <returns>List of selected elements</returns> | |||
IEnumerable<TEntity> GetAll(); | |||
/// <summary> | |||
/// Get all elements of type TEntity in repository | |||
/// </summary> | |||
/// <param name="pageIndex">Page index</param> | |||
/// <param name="pageCount">Number of elements in each page</param> | |||
/// <param name="orderByExpression">Order by expression for this query</param> | |||
/// <param name="ascending">Specify if order is ascending</param> | |||
/// <returns>List of selected elements</returns> | |||
IEnumerable<TEntity> GetPaged<KProperty>(int pageIndex, int pageCount, Expression<Func<TEntity, KProperty>> orderByExpression, bool ascending); | |||
/// <summary> | |||
/// Get elements of type TEntity in repository | |||
/// </summary> | |||
/// <param name="filter">Filter that each element do match</param> | |||
/// <returns>List of selected elements</returns> | |||
IEnumerable<TEntity> GetFiltered(Expression<Func<TEntity, bool>> filter); | |||
} | |||
} |
@ -0,0 +1,46 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork | |||
{ | |||
/// <summary> | |||
/// Contract for ‘UnitOfWork pattern’. For more | |||
/// related info see http://martinfowler.com/eaaCatalog/unitOfWork.html or | |||
/// http://msdn.microsoft.com/en-us/magazine/dd882510.aspx | |||
/// In this Microservice, the Unit Of Work is implemented using the out-of-box | |||
/// Entity Framework Context (EF Core DbContext) persistence engine. But in order to | |||
/// comply the PI (Persistence Ignorant) principle in our Domain, we implement this interface/contract. | |||
/// This interface/contract should be complied by any UoW implementation to be used with this Domain. | |||
/// </summary> | |||
public interface IUnitOfWork | |||
: IDisposable | |||
{ | |||
/// <summary> | |||
/// Commit all changes made in a container. | |||
/// </summary> | |||
///<remarks> | |||
/// If the entity have fixed properties and any optimistic concurrency problem exists, | |||
/// then an exception is thrown | |||
///</remarks> | |||
void Commit(); | |||
/// <summary> | |||
/// Commit all changes made in a container. | |||
/// </summary> | |||
///<remarks> | |||
/// If the entity have fixed properties and any optimistic concurrency problem exists, | |||
/// then 'client changes' are refreshed - Client wins | |||
///</remarks> | |||
void CommitAndRefreshChanges(); | |||
/// <summary> | |||
/// Rollback tracked changes. See references of UnitOfWork pattern | |||
/// </summary> | |||
void RollbackChanges(); | |||
} | |||
} | |||
@ -0,0 +1,47 @@ | |||
using System; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork | |||
{ | |||
internal static class IdentityGenerator | |||
{ | |||
/// <summary> | |||
/// This algorithm generates secuential GUIDs across system boundaries, ideal for databases | |||
/// </summary> | |||
/// <returns></returns> | |||
public static Guid NewSequentialGuid() | |||
{ | |||
byte[] uid = Guid.NewGuid().ToByteArray(); | |||
byte[] binDate = BitConverter.GetBytes(DateTime.UtcNow.Ticks); | |||
byte[] secuentialGuid = new byte[uid.Length]; | |||
secuentialGuid[0] = uid[0]; | |||
secuentialGuid[1] = uid[1]; | |||
secuentialGuid[2] = uid[2]; | |||
secuentialGuid[3] = uid[3]; | |||
secuentialGuid[4] = uid[4]; | |||
secuentialGuid[5] = uid[5]; | |||
secuentialGuid[6] = uid[6]; | |||
// set the first part of the 8th byte to '1100' so | |||
// later we'll be able to validate it was generated by us | |||
secuentialGuid[7] = (byte)(0xc0 | (0xf & uid[7])); | |||
// the last 8 bytes are sequential, | |||
// it minimizes index fragmentation | |||
// to a degree as long as there are not a large | |||
// number of Secuential-Guids generated per millisecond | |||
secuentialGuid[9] = binDate[0]; | |||
secuentialGuid[8] = binDate[1]; | |||
secuentialGuid[15] = binDate[2]; | |||
secuentialGuid[14] = binDate[3]; | |||
secuentialGuid[13] = binDate[4]; | |||
secuentialGuid[12] = binDate[5]; | |||
secuentialGuid[11] = binDate[6]; | |||
secuentialGuid[10] = binDate[7]; | |||
return new Guid(secuentialGuid); | |||
} | |||
} | |||
} |
@ -0,0 +1,145 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using System.Reflection; | |||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork | |||
{ | |||
/// <summary> | |||
/// Base class for value objects in domain. | |||
/// Value | |||
/// </summary> | |||
/// <typeparam name="TValueObject">The type of this value object</typeparam> | |||
public class ValueObject<TValueObject> : IEquatable<TValueObject> | |||
where TValueObject : ValueObject<TValueObject> | |||
{ | |||
//IEquatable and Override Equals operators | |||
/// <summary> | |||
/// <see cref="M:System.Object.IEquatable{TValueObject}"/> | |||
/// </summary> | |||
/// <param name="other"><see cref="M:System.Object.IEquatable{TValueObject}"/></param> | |||
/// <returns><see cref="M:System.Object.IEquatable{TValueObject}"/></returns> | |||
public bool Equals(TValueObject other) | |||
{ | |||
if ((object)other == null) | |||
return false; | |||
if (Object.ReferenceEquals(this, other)) | |||
return true; | |||
//compare all public properties | |||
PropertyInfo[] publicProperties = this.GetType().GetProperties(); | |||
if ((object)publicProperties != null | |||
&& | |||
publicProperties.Any()) | |||
{ | |||
return publicProperties.All(p => | |||
{ | |||
var left = p.GetValue(this, null); | |||
var right = p.GetValue(other, null); | |||
if (typeof(TValueObject).IsAssignableFrom(left.GetType())) | |||
{ | |||
//check not self-references... | |||
return Object.ReferenceEquals(left, right); | |||
} | |||
else | |||
return left.Equals(right); | |||
}); | |||
} | |||
else | |||
return true; | |||
} | |||
/// <summary> | |||
/// <see cref="M:System.Object.Equals"/> | |||
/// </summary> | |||
/// <param name="obj"><see cref="M:System.Object.Equals"/></param> | |||
/// <returns><see cref="M:System.Object.Equals"/></returns> | |||
public override bool Equals(object obj) | |||
{ | |||
if ((object)obj == null) | |||
return false; | |||
if (Object.ReferenceEquals(this, obj)) | |||
return true; | |||
ValueObject<TValueObject> item = obj as ValueObject<TValueObject>; | |||
if ((object)item != null) | |||
return Equals((TValueObject)item); | |||
else | |||
return false; | |||
} | |||
/// <summary> | |||
/// <see cref="M:System.Object.GetHashCode"/> | |||
/// </summary> | |||
/// <returns><see cref="M:System.Object.GetHashCode"/></returns> | |||
public override int GetHashCode() | |||
{ | |||
int hashCode = 31; | |||
bool changeMultiplier = false; | |||
int index = 1; | |||
//compare all public properties | |||
PropertyInfo[] publicProperties = this.GetType().GetProperties(); | |||
if ((object)publicProperties != null | |||
&& | |||
publicProperties.Any()) | |||
{ | |||
foreach (var item in publicProperties) | |||
{ | |||
object value = item.GetValue(this, null); | |||
if ((object)value != null) | |||
{ | |||
hashCode = hashCode * ((changeMultiplier) ? 59 : 114) + value.GetHashCode(); | |||
changeMultiplier = !changeMultiplier; | |||
} | |||
else | |||
hashCode = hashCode ^ (index * 13);//only for support {"a",null,null,"a"} <> {null,"a","a",null} | |||
} | |||
} | |||
return hashCode; | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
/// <param name="left"></param> | |||
/// <param name="right"></param> | |||
/// <returns></returns> | |||
public static bool operator ==(ValueObject<TValueObject> left, ValueObject<TValueObject> right) | |||
{ | |||
if (Object.Equals(left, null)) | |||
return (Object.Equals(right, null)) ? true : false; | |||
else | |||
return left.Equals(right); | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
/// <param name="left"></param> | |||
/// <param name="right"></param> | |||
/// <returns></returns> | |||
public static bool operator !=(ValueObject<TValueObject> left, ValueObject<TValueObject> right) | |||
{ | |||
return !(left == right); | |||
} | |||
} | |||
} | |||