|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- > 领域设计
-
- - 所有的领域应该有一个抽象基类,而且是一个泛型
- ```
- public abstract class Entity<T>
- {
- public T Id{get;protected set;}
- }
- ```
-
- - DDD中有一个很重要的概念,叫作Object Value Type。会把一个Aggregate里的对象类型看作是值类型。这样做一方面是减少Aggregate对关系的依赖,一方面可以很好判断Aggregage的状态。同样也是一个泛型抽象基类,如果一个Object类型的对象想要被当作值类型处理,那就继承这个泛型抽象基类。
- - object value type怎么理解
- - 值对象 主要描述领域的某个方面而本身没有概念表示的对象。
- - 主要是为了方便比较领域对象。
- - 以前
- ```
- public class Order
- {
- public List<OrderLine> Lines{get;set;}
- public Address Address{get;set;}
- }
- ```
- - 实际上Order里面包含了对象类型,是很难比较的
- - 现在
- ```
- public class OrderLine:ValueObject<OrderLine>,
- public class Address:ValueObject<Address>
- ```
- - 这就把原来是引用类型的对象OrderLine和Address转换成值类型了,这就减少了对象之间的相互依赖。
- ```
- public abstract class ValueObject<T> where T : ValueObject<T>
- {
- /// <summary>
- /// 获取子类需要比较的属性
- /// 抽象方法,通过子类override实现
- /// </summary>
- protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();
-
- /// <summary>
- /// 比较
- /// 可以被子类重写
- /// </summary>
- public virtual bool Equals(T other)
- {
- if (other == null) return false;
-
- //两个对象的属性值比较
- return GetAttributesToIncludeInEqualityCheck().SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
- }
-
- /// <summary>
- /// 重写object基类的比较大小的方法
- /// </summary>
- public override bool Equals(object other)
- {
- return Equals(other as T);
- }
-
- /// <summary>
- /// 重写object基类的GetHashCode
- /// </summary>
- public override int GetHashCode()
- {
- var hash = 17;
- foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
- {
- hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());
- }
- return hash;
- }
-
- /// <summary>
- /// 重新定义==
- /// </summary>
- public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
- {
- return Equals(left, right);
- }
-
- /// <summary>
- /// 重新定义=
- /// </summary>
-
- public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
- {
- return !(left==right);
- }
- }
-
- ```
- - 以上,ValueObject<T>就是Object Value Type的抽象基类,大致作了如下事情:
-
- - 重写基类object的Equals方法
- - 重写基类object的GetHashCode方法
- - 提供一个抽象方法,让子类根据各自不同的属性比较大小,让子类重写
- - 有一个自定义的比较大小的方法,会调用基类的抽象方法
- - 重新定义==和!=
-
- - 所有的领域也好,或者Aggregate也好,都需要事件驱动。关于事件,同样需要一个基类。
- ```
- public abstract class DomainEvent
- {
- public Guid Id { get; protected set; }
- public DateTime OccuredOn { get; protected set; }
-
- protected DomainEvent(Guid id, DateTime occuredOn)
- {
- Id = id;
- OccuredOn = occuredOn;
- }
-
- protected DomainEvent()
- {
- Id = Guid.NewGuid();
- OccuredOn = SysTime.Now();
- }
- }
- 以上,DomainEvent就是事件的抽象基类。
- ```
- - 一个属性是Guid类型,这是很有特色的一个点,事件居然也有Guid类型,就像很多领域都有主键一样
- - 在DDD中,完全可以实现Event Sourcing,事件追溯。注意,这里的Event Sourcing和前面说的Event Storming不一样。Event Soucing适合的场景包括:银行、保险公司等。对于一个领域来说,只要执行了相同数量、相同顺序的事件,领域就会来到一个确定的状态。据说,前端框架Redux就用到了Event Stroming,让领域的状态可追溯。当然,一般的项目用不上Event Sourcing。
- - 以上,DateTime类型的属性OccurredOn的值是通过一个静态方法获取到的。这样做有什么好处呢?
- ```
- public class SysTime
- {
- public static Func<DateTime> CurrentTimeProvider { get; set; } = () => DateTime.Now;
-
- public static DateTime Now() => CurrentTimeProvider();
- }
- ```
- - 以上,Now方法类似方法内的一个工厂方法,产生DateTime的实例。但实例是通过CurrentTimeProvider这个委托得到的。不难理解,通过委托获取DateTime实例增加了灵活性。所谓的委托,就是定义好输入形参和返回类型,委托就是一个方法,把方法看作一个变量,调用委托,就是调用方法。
-
- - 以上,有了领域的基类Entity, 有了Object Value Type, 有了事件的基类。接下来,事件需要被发布,这样系统内都某个事件订阅者能获悉并执行。所以,事件发布需要一个接口:
- ```
- public interface IEventPublisher
- {
- void Publish(DomainEvent @event);
- }
- ```
- - 以上,所有的要么是抽象基类,要么是接口,我们可以体会到:在领域层基本都是抽象的。也就是在我们所熟悉的洋葱架构中,越内层的越抽象,越外层越具体,越抽象也意味着更低的耦合性、更好的扩展性。而且,领域层,乃至领域层的外面一层都是抽象居多,这一点也方便单元测试。
-
-
-
- - 接下来,会把Event Storming中的设计拿来,考虑如何定义领域
- - 首先,所有的领域都会有主键,通常使用Guid类型,但是Guid类型是一个Object类型,这在DDD的领域设计中需要把之转换成值类型,也就是Object Value Type。
- ```
- public class EntityId : ValueObject<EntityId>
- {
- protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
- {
- yield return Value;
- }
-
- public Guid Value { get; }
-
- public EntityId(Guid value)
- {
- Value = value;
- }
-
- protected EntityId() { }
- }
-
- ```
- - 以上,EntityId就是Object Value Type,派生于ValueObject<T>。GetAttributesToIncludeInEqualityCheck方法是重写或者说实现基类的方法,当ValueObject的Equals方法的时候会调用GetAttributesToIncludeInEqualityCheck方法。EntityId有一个比较奇怪的地方,就是有个无参的构造函数,这个是为EF Core准备的。
-
-
- - 接着,创建第一个领域或者说Aggregate:
- ```
- /// <summary>
- /// 报警
- /// </summary>
- public class Warning : Entity<EntityId>
- {
- }
- ```
- - 以上,Warning派生于领域抽象基类Entity<T>,这里的T表示领域的主键类型,这里的主键类型是EntityId,也就是Object Value Type, 可以看作是一个像int类型那样的值类型。
-
|