diff --git a/src/Services/Ordering/Ordering.API/DTO/OrderDTO.cs b/src/Services/Ordering/Ordering.API/DTO/OrderDTO.cs new file mode 100644 index 000000000..39abc372f --- /dev/null +++ b/src/Services/Ordering/Ordering.API/DTO/OrderDTO.cs @@ -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 + { + } +} diff --git a/src/Services/Ordering/Ordering.API/DTO/OrderItemDTO.cs b/src/Services/Ordering/Ordering.API/DTO/OrderItemDTO.cs new file mode 100644 index 000000000..1b25b28d9 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/DTO/OrderItemDTO.cs @@ -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); + } + } +} diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/Buyer/Buyer.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Buyer/Buyer.cs new file mode 100644 index 000000000..eaa041a12 --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Buyer/Buyer.cs @@ -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; + } + } +} diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Address.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Address.cs new file mode 100644 index 000000000..0e27b23ec --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Address.cs @@ -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
//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; + } + } +} diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Order.cs new file mode 100644 index 000000000..92b3763b8 --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Order.cs @@ -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 _orderItems; + public virtual List orderItems + { + get + { + if (_orderItems == null) + _orderItems = new List(); + + 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; + } + + } +} diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/OrderItem.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/OrderItem.cs new file mode 100644 index 000000000..6290c1e81 --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/OrderItem.cs @@ -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); + } + } +} diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/OrderStatus.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/OrderStatus.cs new file mode 100644 index 000000000..e48aaaaf1 --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/OrderStatus.cs @@ -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, + } +} diff --git a/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs b/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs new file mode 100644 index 000000000..ae6756f19 --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs @@ -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 + { + } +} diff --git a/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs b/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs new file mode 100644 index 000000000..bcb55f171 --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs @@ -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 Get(int associatedConferenceId); + + void ModifyOrder(Order seatOrder); + } +} diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/AggregateRoot.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/AggregateRoot.cs index 85d993536..99304f88b 100644 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/AggregateRoot.cs +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/AggregateRoot.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork { - public class AggregateRoot + public class AggregateRoot : Entity { } } diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs index 2452f8e29..258b364e5 100644 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs @@ -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 + /// + /// Base class for entities + /// + public abstract class Entity { + //Members + + int? _requestedHashCode; + Guid _Id; + + + //Properties + + /// + /// Get the persisten object identifier + /// + public virtual Guid Id + { + get + { + return _Id; + } + protected set + { + _Id = value; + } + } + + //Public Methods + + /// + /// Check if this entity is transient, ie, without identity at this moment + /// + /// True if entity is transient, else false + public bool IsTransient() + { + return this.Id == Guid.Empty; + } + + /// + /// Generate identity for this entity + /// + public void GenerateNewIdentity() + { + if ( IsTransient()) + this.Id = IdentityGenerator.NewSequentialGuid(); + } + + /// + /// Change current identity for a new non transient identity + /// + /// the new identity + public void ChangeCurrentIdentity(Guid identity) + { + if ( identity != Guid.Empty) + this.Id = identity; + } + + + //Overrides Methods + + /// + /// + /// + /// + /// + 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; + } + + /// + /// + /// + /// + 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(); + + } + /// + /// equals operator + /// + /// left parameter + /// right parameter + /// true if equals + 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); + } + + /// + /// Distinct operator + /// + /// left parameter + /// right parameter + /// True if different + public static bool operator !=(Entity left, Entity right) + { + return !(left == right); + } + } } diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs new file mode 100644 index 000000000..e2a7f43ba --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs @@ -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 +{ + + /// + /// 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 + /// + /// + /// 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 + /// + /// Type of entity for this repository + public interface IRepository : IDisposable + where TEntity : Entity + { + /// + /// Get the unit of work in this repository + /// + IUnitOfWork UnitOfWork { get; } + + /// + /// Add item into repository + /// + /// Item to add to repository + void Add(TEntity item); + + /// + /// Delete item + /// + /// Item to delete + void Remove(TEntity item); + + /// + /// Set item as modified + /// + /// Item to modify + void Modify(TEntity item); + + /// + ///Track entity into this repository, really in UnitOfWork. + ///In EF this can be done with Attach and with Update in NH + /// + /// Item to attach + void TrackItem(TEntity item); + + /// + /// Sets modified entity into the repository. + /// When calling Commit() method in UnitOfWork + /// these changes will be saved into the storage + /// + /// The persisted item + /// The current item + void Merge(TEntity persisted, TEntity current); + + /// + /// Get element by entity key + /// + /// Entity key value + /// + TEntity Get(Guid id); + + /// + /// Get all elements of type TEntity in repository + /// + /// List of selected elements + IEnumerable GetAll(); + + /// + /// Get all elements of type TEntity in repository + /// + /// Page index + /// Number of elements in each page + /// Order by expression for this query + /// Specify if order is ascending + /// List of selected elements + IEnumerable GetPaged(int pageIndex, int pageCount, Expression> orderByExpression, bool ascending); + + /// + /// Get elements of type TEntity in repository + /// + /// Filter that each element do match + /// List of selected elements + IEnumerable GetFiltered(Expression> filter); + } +} diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs new file mode 100644 index 000000000..e9c2f3935 --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork +{ + /// + /// 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. + /// + public interface IUnitOfWork + : IDisposable + { + /// + /// Commit all changes made in a container. + /// + /// + /// If the entity have fixed properties and any optimistic concurrency problem exists, + /// then an exception is thrown + /// + void Commit(); + + /// + /// Commit all changes made in a container. + /// + /// + /// If the entity have fixed properties and any optimistic concurrency problem exists, + /// then 'client changes' are refreshed - Client wins + /// + void CommitAndRefreshChanges(); + + + /// + /// Rollback tracked changes. See references of UnitOfWork pattern + /// + void RollbackChanges(); + + } +} + diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/IdentityGenerator.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/IdentityGenerator.cs new file mode 100644 index 000000000..bc880ed78 --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/IdentityGenerator.cs @@ -0,0 +1,47 @@ +using System; + +namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork +{ + internal static class IdentityGenerator + { + /// + /// This algorithm generates secuential GUIDs across system boundaries, ideal for databases + /// + /// + 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); + } + } +} diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/ValueObject.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/ValueObject.cs new file mode 100644 index 000000000..9369e6a7d --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/ValueObject.cs @@ -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 +{ + /// + /// Base class for value objects in domain. + /// Value + /// + /// The type of this value object + public class ValueObject : IEquatable + where TValueObject : ValueObject + { + + //IEquatable and Override Equals operators + + /// + /// + /// + /// + /// + 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; + } + /// + /// + /// + /// + /// + public override bool Equals(object obj) + { + if ((object)obj == null) + return false; + + if (Object.ReferenceEquals(this, obj)) + return true; + + ValueObject item = obj as ValueObject; + + if ((object)item != null) + return Equals((TValueObject)item); + else + return false; + + } + /// + /// + /// + /// + 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; + } + /// + /// + /// + /// + /// + /// + public static bool operator ==(ValueObject left, ValueObject right) + { + if (Object.Equals(left, null)) + return (Object.Equals(right, null)) ? true : false; + else + return left.Equals(right); + + } + /// + /// + /// + /// + /// + /// + public static bool operator !=(ValueObject left, ValueObject right) + { + return !(left == right); + } + + + } +} + diff --git a/src/Services/Ordering/Ordering.Domain/project.json b/src/Services/Ordering/Ordering.Domain/project.json index 864b9a5f3..bcfd892ef 100644 --- a/src/Services/Ordering/Ordering.Domain/project.json +++ b/src/Services/Ordering/Ordering.Domain/project.json @@ -1,8 +1,10 @@ -{ +{ "version": "1.0.0-*", "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": {