重新整理 .net core 实践篇—————Entity的定义[二十五]

Posted 你永远想象不到,一个光鲜亮丽的Application,有多么

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重新整理 .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. 领域服务或命令处理者扶着调用领域模型业务动作

下一节 领域模型之工作单元模式

以上是关于重新整理 .net core 实践篇—————Entity的定义[二十五]的主要内容,如果未能解决你的问题,请参考以下文章

重新整理 .net core 实践篇—————中间件[十九]

重新整理 .net core 实践篇—————应用层[三十]

重新整理 .net core 实践篇—————应用分层[二十四]

重新整理 .net core 实践篇—————日志系统之战地记者[十五]

重新整理 .net core 实践篇—————工作单元模式[二十六]

重新整理 .net core 实践篇—————仓储层的具体实现[二十七]