Kaynağa Gözat

DDD领域设计

master
unknown 4 yıl önce
ebeveyn
işleme
ba19ea289a

实践/后端/项目/imgs/26.DDD微服务.md → 实践/后端/项目/26.DDD微服务.md Dosyayı Görüntüle


实践/后端/项目/imgs/27.DDD事件风暴.md → 实践/后端/项目/27.DDD事件风暴.md Dosyayı Görüntüle


+ 0
- 0
实践/后端/项目/28.领域设计.md Dosyayı Görüntüle


+ 178
- 0
实践/后端/项目/imgs/28.领域设计.md Dosyayı Görüntüle

@@ -0,0 +1,178 @@
1
+> 领域设计
2
+
3
+- 所有的领域应该有一个抽象基类,而且是一个泛型
4
+```
5
+public abstract class Entity<T>
6
+{
7
+    public T Id{get;protected set;}
8
+}
9
+```
10
+
11
+- DDD中有一个很重要的概念,叫作Object Value Type。会把一个Aggregate里的对象类型看作是值类型。这样做一方面是减少Aggregate对关系的依赖,一方面可以很好判断Aggregage的状态。同样也是一个泛型抽象基类,如果一个Object类型的对象想要被当作值类型处理,那就继承这个泛型抽象基类。
12
+- object value type怎么理解
13
+  - 值对象 主要描述领域的某个方面而本身没有概念表示的对象。
14
+  - 主要是为了方便比较领域对象。
15
+    - 以前
16
+    ```
17
+    public class Order
18
+    {
19
+        public List<OrderLine> Lines{get;set;}
20
+        public Address Address{get;set;}
21
+    }
22
+    ```
23
+    - 实际上Order里面包含了对象类型,是很难比较的
24
+    - 现在
25
+    ```
26
+    public class OrderLine:ValueObject<OrderLine>,
27
+    public class Address:ValueObject<Address>
28
+    ```
29
+    - 这就把原来是引用类型的对象OrderLine和Address转换成值类型了,这就减少了对象之间的相互依赖。
30
+```
31
+ public abstract class ValueObject<T> where T : ValueObject<T>
32
+    {
33
+        /// <summary>
34
+        /// 获取子类需要比较的属性
35
+        /// 抽象方法,通过子类override实现
36
+        /// </summary>
37
+        protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();
38
+
39
+        /// <summary>
40
+        /// 比较
41
+        /// 可以被子类重写
42
+        /// </summary>
43
+        public virtual bool Equals(T other)
44
+        {
45
+            if (other == null) return false;
46
+
47
+            //两个对象的属性值比较
48
+            return GetAttributesToIncludeInEqualityCheck().SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
49
+        }
50
+
51
+        /// <summary>
52
+        /// 重写object基类的比较大小的方法
53
+        /// </summary>
54
+        public override bool Equals(object other)
55
+        {
56
+            return Equals(other as T);
57
+        }
58
+
59
+        /// <summary>
60
+        /// 重写object基类的GetHashCode
61
+        /// </summary>
62
+        public override int GetHashCode()
63
+        {
64
+            var hash = 17;
65
+            foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
66
+            {
67
+                hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());
68
+            }
69
+            return hash;
70
+        }
71
+
72
+        /// <summary>
73
+        /// 重新定义==
74
+        /// </summary>
75
+        public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
76
+        {
77
+            return Equals(left, right);
78
+        }
79
+
80
+        /// <summary>
81
+        /// 重新定义=
82
+        /// </summary>
83
+
84
+        public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
85
+        {
86
+            return !(left==right);
87
+        }
88
+    }
89
+
90
+```
91
+- 以上,ValueObject<T>就是Object Value Type的抽象基类,大致作了如下事情:
92
+
93
+   - 重写基类object的Equals方法
94
+   - 重写基类object的GetHashCode方法
95
+   - 提供一个抽象方法,让子类根据各自不同的属性比较大小,让子类重写
96
+   - 有一个自定义的比较大小的方法,会调用基类的抽象方法
97
+   - 重新定义==和!=
98
+
99
+- 所有的领域也好,或者Aggregate也好,都需要事件驱动。关于事件,同样需要一个基类。
100
+```
101
+ public abstract class DomainEvent
102
+    {
103
+        public Guid Id { get; protected set; }
104
+        public DateTime OccuredOn { get; protected set; }
105
+
106
+        protected DomainEvent(Guid id, DateTime occuredOn)
107
+        {
108
+            Id = id;
109
+            OccuredOn = occuredOn;
110
+        }
111
+
112
+        protected DomainEvent()
113
+        {
114
+            Id = Guid.NewGuid();
115
+            OccuredOn = SysTime.Now();
116
+        }
117
+    }
118
+    以上,DomainEvent就是事件的抽象基类。
119
+```
120
+- 一个属性是Guid类型,这是很有特色的一个点,事件居然也有Guid类型,就像很多领域都有主键一样
121
+- 在DDD中,完全可以实现Event Sourcing,事件追溯。注意,这里的Event Sourcing和前面说的Event Storming不一样。Event Soucing适合的场景包括:银行、保险公司等。对于一个领域来说,只要执行了相同数量、相同顺序的事件,领域就会来到一个确定的状态。据说,前端框架Redux就用到了Event Stroming,让领域的状态可追溯。当然,一般的项目用不上Event Sourcing。
122
+- 以上,DateTime类型的属性OccurredOn的值是通过一个静态方法获取到的。这样做有什么好处呢?
123
+```
124
+public class SysTime
125
+    {
126
+        public static Func<DateTime> CurrentTimeProvider { get; set; } = () => DateTime.Now;
127
+
128
+        public static DateTime Now() => CurrentTimeProvider();
129
+    }
130
+```
131
+- 以上,Now方法类似方法内的一个工厂方法,产生DateTime的实例。但实例是通过CurrentTimeProvider这个委托得到的。不难理解,通过委托获取DateTime实例增加了灵活性。所谓的委托,就是定义好输入形参和返回类型,委托就是一个方法,把方法看作一个变量,调用委托,就是调用方法。
132
+
133
+- 以上,有了领域的基类Entity, 有了Object Value Type, 有了事件的基类。接下来,事件需要被发布,这样系统内都某个事件订阅者能获悉并执行。所以,事件发布需要一个接口:
134
+```
135
+ public interface IEventPublisher
136
+    {
137
+        void Publish(DomainEvent @event);
138
+    }
139
+```
140
+- 以上,所有的要么是抽象基类,要么是接口,我们可以体会到:在领域层基本都是抽象的。也就是在我们所熟悉的洋葱架构中,越内层的越抽象,越外层越具体,越抽象也意味着更低的耦合性、更好的扩展性。而且,领域层,乃至领域层的外面一层都是抽象居多,这一点也方便单元测试。
141
+
142
+
143
+
144
+- 接下来,会把Event Storming中的设计拿来,考虑如何定义领域
145
+- 首先,所有的领域都会有主键,通常使用Guid类型,但是Guid类型是一个Object类型,这在DDD的领域设计中需要把之转换成值类型,也就是Object Value Type。
146
+```
147
+public class EntityId : ValueObject<EntityId>
148
+    {
149
+        protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
150
+        {
151
+            yield return Value;
152
+        }
153
+
154
+        public Guid Value { get; }
155
+
156
+        public EntityId(Guid value)
157
+        {
158
+            Value = value;
159
+        }
160
+
161
+        protected EntityId() { }
162
+    }
163
+
164
+```
165
+- 以上,EntityId就是Object Value Type,派生于ValueObject<T>。GetAttributesToIncludeInEqualityCheck方法是重写或者说实现基类的方法,当ValueObject的Equals方法的时候会调用GetAttributesToIncludeInEqualityCheck方法。EntityId有一个比较奇怪的地方,就是有个无参的构造函数,这个是为EF Core准备的。
166
+
167
+
168
+- 接着,创建第一个领域或者说Aggregate:
169
+```
170
+ /// <summary>
171
+    /// 报警
172
+    /// </summary>
173
+    public class Warning : Entity<EntityId>
174
+    {
175
+    }
176
+```
177
+- 以上,Warning派生于领域抽象基类Entity<T>,这里的T表示领域的主键类型,这里的主键类型是EntityId,也就是Object Value Type, 可以看作是一个像int类型那样的值类型。
178
+

Loading…
İptal
Kaydet