Browse Source

Work in progress for the Ordering microservice Domain Model

pull/49/merge
Cesar De la Torre 8 years ago
parent
commit
9aed8221f2
16 changed files with 774 additions and 7 deletions
  1. +11
    -0
      src/Services/Ordering/Ordering.API/DTO/OrderDTO.cs
  2. +26
    -0
      src/Services/Ordering/Ordering.API/DTO/OrderItemDTO.cs
  3. +56
    -0
      src/Services/Ordering/Ordering.Domain/AggregatesModel/Buyer/Buyer.cs
  4. +64
    -0
      src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Address.cs
  5. +77
    -0
      src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Order.cs
  6. +35
    -0
      src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/OrderItem.cs
  7. +18
    -0
      src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/OrderStatus.cs
  8. +11
    -0
      src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs
  9. +17
    -0
      src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs
  10. +1
    -1
      src/Services/Ordering/Ordering.Domain/SeedWork/AggregateRoot.cs
  11. +123
    -4
      src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs
  12. +93
    -0
      src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs
  13. +46
    -0
      src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs
  14. +47
    -0
      src/Services/Ordering/Ordering.Domain/SeedWork/IdentityGenerator.cs
  15. +145
    -0
      src/Services/Ordering/Ordering.Domain/SeedWork/ValueObject.cs
  16. +4
    -2
      src/Services/Ordering/Ordering.Domain/project.json

+ 11
- 0
src/Services/Ordering/Ordering.API/DTO/OrderDTO.cs View File

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

+ 26
- 0
src/Services/Ordering/Ordering.API/DTO/OrderItemDTO.cs View File

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

+ 56
- 0
src/Services/Ordering/Ordering.Domain/AggregatesModel/Buyer/Buyer.cs View File

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

+ 64
- 0
src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Address.cs View File

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

+ 77
- 0
src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Order.cs View File

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

+ 35
- 0
src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/OrderItem.cs View File

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

+ 18
- 0
src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/OrderStatus.cs View File

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

+ 11
- 0
src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs View File

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

+ 17
- 0
src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs View File

@ -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
- 1
src/Services/Ordering/Ordering.Domain/SeedWork/AggregateRoot.cs View File

@ -5,7 +5,7 @@ using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork
{ {
public class AggregateRoot
public class AggregateRoot : Entity
{ {
} }
} }

+ 123
- 4
src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs View File

@ -1,11 +1,130 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork 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);
}
} }
} }

+ 93
- 0
src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs View File

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

+ 46
- 0
src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs View File

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

+ 47
- 0
src/Services/Ordering/Ordering.Domain/SeedWork/IdentityGenerator.cs View File

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

+ 145
- 0
src/Services/Ordering/Ordering.Domain/SeedWork/ValueObject.cs View File

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

+ 4
- 2
src/Services/Ordering/Ordering.Domain/project.json View File

@ -1,8 +1,10 @@
{
{
"version": "1.0.0-*", "version": "1.0.0-*",
"dependencies": { "dependencies": {
"NETStandard.Library": "1.6.0"
"NETStandard.Library": "1.6.0",
"System.Reflection.TypeExtensions": "4.1.0",
"System.Runtime.Serialization.Primitives": "4.1.1"
}, },
"frameworks": { "frameworks": {


Loading…
Cancel
Save