1. 程式人生 > 其它 >重新整理 .net core 實踐篇—————Entity的定義[二十五]

重新整理 .net core 實踐篇—————Entity的定義[二十五]

前言

簡單介紹一下實體模型的設計。

正文

前文提及了我們的應用分為:

  1. 共享層

  2. 基礎設施層

  3. 領域層

  4. 應用層

今天來介紹領域模型層。

前文提及到領域模型在共享層有一個領域模型抽象類庫。

裡面有這些類:

先分別介紹一下這些類是做什麼的。

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 定義為了值物件即可。隨著後面系列的他們之間的呼叫會越來越清晰的。

總結

  1. 將領域模型欄位的修改設定為私有的。

  2. 使用建構函式表示物件的建立

  3. 使用具有業務的動作來操作模型欄位

  4. 領域模型複製堆自己資料的處理

  5. 領域模型負責對自己資料的處理

  6. 領域服務或命令處理者扶著呼叫領域模型業務動作

下一節 領域模型之工作單元模式