重新整理 .net core 實踐篇—————Entity的定義[二十五]
阿新 • • 發佈:2021-06-21
前言
簡單介紹一下實體模型的設計。
正文
前文提及了我們的應用分為:
-
共享層
-
基礎設施層
-
領域層
-
應用層
今天來介紹領域模型層。
前文提及到領域模型在共享層有一個領域模型抽象類庫。
裡面有這些類:
先分別介紹一下這些類是做什麼的。
IEntity 類,是我們實體的介面:
/// <summary> /// 實體介面(包含多個主鍵的實體介面) /// </summary> public interface IEntity { object[] GetKeys(); } /// <summary> /// 實體介面(包含唯一主鍵Id的實體介面) /// </summary> /// <typeparam name="TKey">主鍵ID型別</typeparam> public interface IEntity<TKey> : IEntity { TKey Id { get; } }
實現抽象類Entity:
/// <summary> /// 實體抽象類(包含多個主鍵的實體介面) /// </summary> public abstract class Entity : IEntity { public abstract object[] GetKeys(); public override string ToString() { return $"[Entity:{GetType().Name}] Keys = {string.Join(",", GetKeys())}"; } #region 領域事件定義處理 DomainEvents /// <summary> /// 領域事件集合 /// </summary> private List<IDomainEvent> _domainEvents; /// <summary> /// 獲取當前實體物件領域事件集合(只讀) /// </summary> public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly(); /// <summary> /// 新增領域事件至當前實體物件領域事件結合中 /// </summary> /// <param name="eventItem"></param> public void AddDomainEvent(IDomainEvent eventItem) { _domainEvents = _domainEvents ?? new List<IDomainEvent>(); _domainEvents.Add(eventItem); } /// <summary> /// 移除指定領域事件 /// </summary> /// <param name="eventItem"></param> public void RemoveDomainEvent(IDomainEvent eventItem) { _domainEvents?.Remove(eventItem); } /// <summary> /// 清空所有領域事件 /// </summary> public void ClearDomainEvents() { _domainEvents?.Clear(); } #endregion } /// <summary> /// 實體抽象類(包含唯一主鍵Id的實體介面) /// </summary> /// <typeparam name="TKey">主鍵ID型別</typeparam> public abstract class Entity<TKey> : Entity, IEntity<TKey> { int? _requestedHasCode; public virtual TKey Id { get; protected set; } public override object[] GetKeys() { return new object[] { Id }; } /// <summary> /// 物件是否想等 /// </summary> /// <param name="obj"></param> /// <returns></returns> public override bool Equals(object obj) { if (obj == null || !(obj is Entity<TKey>)) { return false; } if (Object.ReferenceEquals(this, obj)) { return true; } Entity<TKey> item = (Entity<TKey>)obj; if (item.IsTransient() || this.IsTransient()) { return false; } else { return item.Id.Equals(this.Id); } } public override int GetHashCode() { if (!IsTransient()) { if (!_requestedHasCode.HasValue) { _requestedHasCode = this.Id.GetHashCode() ^ 31; // TODO } return _requestedHasCode.Value; } else { return base.GetHashCode(); } } /// <summary> /// 物件是否為全新建立的,未持久化的 /// </summary> /// <returns></returns> public bool IsTransient() { return EqualityComparer<TKey>.Default.Equals(Id, default); } public override string ToString() { return $"[Entity:{GetType().Name}] Id = {Id}"; } /// <summary> /// == 操作符過載 /// </summary> /// <param name="left"></param> /// <param name="right"></param> /// <returns></returns> public static bool operator ==(Entity<TKey> left,Entity<TKey> 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 !=(Entity<TKey> left, Entity<TKey> right) { return !(left == right); } }
聚合根介面IAggregateRoot.cs:
/// <summary>
/// 聚合根介面
/// 作用是我們在實現倉儲層的時候,讓我們的一個倉儲對應一個聚合根
/// </summary>
public interface IAggregateRoot
{
}
領域事件IDomainEvent :
/// <summary>
/// 領域事件介面
/// 用來標記我們某一個物件是否是領域事件
/// </summary>
public interface IDomainEvent : INotification
{
}
領域事件的處理介面IDomainEventHandler:
/// <summary> /// 領域事件處理器介面 /// </summary> public interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent> where TDomainEvent : IDomainEvent { //這裡我們使用了INotificationHandler的Handle方法來作為處理方法的定義,所以無需重新定義 //Task Handle(TDomainEvent domainEvent, CancellationToken cancellationToken); }
值物件 ValueObject:
/// <summary>
/// 值物件
/// TODO 領域驅動中比較關鍵的類
/// </summary>
public abstract class ValueObject
{
protected static bool EqualOperator(ValueObject left, ValueObject right)
{
if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null))
{
return false;
}
return ReferenceEquals(left, null) || left.Equals(right);
}
protected static bool NotEqualQperator(ValueObject left, ValueObject right)
{
return !(EqualOperator(left, right));
}
/// <summary>
/// 獲取值物件原子值(欄位值)
/// 將我們值物件的欄位輸出出來,作為唯一標識來判斷我們兩個物件是否想等
/// </summary>
/// <returns></returns>
protected abstract IEnumerable<object> GetAtomicValues();
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
{
return false;
}
ValueObject other = (ValueObject)obj;
IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator();
IEnumerator<object> otherValues = other.GetAtomicValues().GetEnumerator();
while (thisValues.MoveNext() && otherValues.MoveNext())
{
if (ReferenceEquals(thisValues.Current, null) ^ ReferenceEquals(otherValues.Current, null))
{
return false;
}
if (thisValues.Current != null && !thisValues.Current.Equals(otherValues.Current))
{
return false;
}
}
return !thisValues.MoveNext() && !otherValues.MoveNext();
}
public override int GetHashCode()
{
return GetAtomicValues()
.Select(x => x != null ? x.GetHashCode() : 0)
.Aggregate((x, y) => x ^ y);
}
}
那麼來看一下領域模型的具體實現:
先來看一下Aggregate 的具體實現:
/// <summary>
/// 訂單實體
/// </summary>
public class Order : Entity<long>, IAggregateRoot
{
// 實體內欄位的 set 方法都是 private 的
// 實體型別相關的資料操作,都應該是由我們實體來負責,而不是被外部的物件去操作
// 這樣的好處是讓我們的領域模型符合封閉開放的原則
public string UserId { get; private set; }
public string UserName { get; private set; }
public Address Address { get; private set; }
public int ItemCount { get; set; }
protected Order()
{
}
public Order(string userId, string userName, int itemCount, Address address)
{
this.UserId = userId;
this.UserName = userName;
this.ItemCount = itemCount;
this.Address = address;
// 構造新的Order物件的時候,新增一個建立Order領域事件
this.AddDomainEvent(new OrderCreatedDomainEvent(this));
}
/// <summary>
/// 修改收貨地址
/// </summary>
/// <param name="address"></param>
public void ChangeAddress(Address address)
{
this.Address = address;
// 同樣的,在修改地址操作時,也該定義一個類似的修改地址領域事件
//this.AddDomainEvent(new OrderAddressChangedDomainEvent(this));
}
}
這裡面實現了Entity,同時實現了IAggregateRoot,這個接口裡面沒有任何東西,表示這是定義介面,表示將這個Order 定義為一個聚合根。
看一下Address:
/// <summary>
/// 地址實體
/// 定義為值物件
/// </summary>
public class Address : ValueObject
{
public string Street { get; private set; }
public string City { get; private set; }
public string ZipCode { get; private set; }
public Address()
{
}
public Address(string street, string city, string zipCode)
{
this.Street = street;
this.City = city;
this.ZipCode = zipCode;
}
/// <summary>
/// 過載獲取原子值的方法
/// 這裡特殊的是,我們使用了 yield return 的方式
/// </summary>
/// <returns></returns>
protected override IEnumerable<object> GetAtomicValues()
{
yield return Street;
yield return City;
yield return ZipCode;
}
}
將這個Address 定義為值物件。
梳理
這裡如果不瞭解領域設計,會有點蒙。
那麼這裡只需要知道這裡Order 定義為了aggregateRoot,也就是聚合根。
把Address 定義為了值物件即可。隨著後面系列的他們之間的呼叫會越來越清晰的。
總結
-
將領域模型欄位的修改設定為私有的。
-
使用建構函式表示物件的建立
-
使用具有業務的動作來操作模型欄位
-
領域模型複製堆自己資料的處理
-
領域模型負責對自己資料的處理
-
領域服務或命令處理者扶著呼叫領域模型業務動作
結
下一節 領域模型之工作單元模式