北京移动,基于柔性制造的DDD战术设计实践-安博电竞APP下载ios-anggame安博电竞app

国际新闻 313℃ 0

导言

DDD(Domain Driven Design,范畴驱动规划)最早由 Eric Evans 提出,是一种以范畴为中心驱动力的敞开的规划办法系统。这 15 年以来,DDD 一直在刚强的成长,尤其是微服务盛行的这几年,DDD 从头焕发了芳华,国内越来越多的技能爱好者开端重视并实践 DDD。

DDD 作为一种敞开的规划系统,不断容纳新的实践办法,比方范畴事情、事情溯源模明星凸点式、CQ北京移动,依据柔性制作的DDD战术规划实践-安博电竞APP下载ios-anggame安博电竞appRS 形式、事情风暴、六边形架构、洋葱架构、整齐架构,一起还引进 FP(Functional Programming,函数式)编程思维,运用纯函数与笼统代数结构的不变性以及函数的组合性来表达范畴模型。

范畴专家和开发团队严密地作业在一起,先经过战略规划合理的划分出 BC(Bounded Context,限界上下文),然后在 BC 内经过战术规划得到共同共同的无歧义的范畴模型。范畴模型是软件开发进程中最中心的财物,各个人物都环绕范畴模型无障碍的交流。跟着需求的不断添加,各个人物对软件要处理的事务问题和处理办法有了更深化的了解,凭借重构和自动化测验等技能实践不断演进范畴模型,下降了软件的完成杂乱度,使得软件愈加贴合事务的实质。

依据 FP 进行范畴建模的场景

DDD 在战术规划实践中应对的是范畴的杂乱性,并不牵涉技能层面的完成细节,其首要的规划元素包含:

  • 值方针(Value Object)
  • 实体(Entity)
  • 范畴服务(Domain Service)
  • 范畴事情(D张狂动物城图片omain Event)
  • 资源库(Repository)
  • 工厂(Factory)
  • 聚合(Aggregate)
  • 应用服务(Application Service)

Eric Evans 经过下图勾勒出了战术规划各元素之间的联系:


范畴模型是从范畴问题动身人为构建的一种面向范畴的指示性语义,挑选某种编程范式就选定了特定的构建根底。理论上不论挑选 OP(面向进程)、OO(面向方针)仍是 FP(函数式)做为构建根底都是图灵齐备的,但在工程上需求考量哪种编程范式与范畴语义之间的 Gap 最小且保护本钱最低。别的现代编程言语根本都支撑多范式,使得程序员能够在部分灵敏挑选最佳的编程范式。

人们在依据 OO 的范畴建模方面现已积累了大随身仙田空间量的经历,而在依据 FP 的范畴建模方面的经历却比较匮乏。笔者曾经的 DDD 战术规划实践都是习惯性的依据 OO 编程范式的,常常会用到战术规划的各种元素。直到在一次 DDD 战训营时,小伙伴们在教练的引导下针对告警子域的 BC 进行战术规划,最终发现告警规矩、告警防抖或告警消重相关的范畴概念具有不行变性,都能够建模为值方针,然后依据 FP 来进行的范畴建模和范畴语义之间的 Gap 最小且保护本钱最低。

一说值方针,许多同学就想到不行变性,其实不一定。Eric Evans 在书中着重了“何时答应可变性”的特殊情况:

坚持 VALUE OBJECT 不变能够极大地简化完成,并保证同享和引证传递的安全性,并且这样做也符和值的意义。假如特点的值发作改动,咱们应该运用一个不同的 VALUE OBJECT,而不是修正现有的 VALUE OBJECT。尽管如此,在有些情况下出于功能考虑,让需求让 VALUE OBJECT 是可变的,这包含以下要素:
  • 假如 VALUE 频频改动;
  • 假如创立或删去方针的开支很大;
  • 假如替换(而不是修正)将打乱集群(像前面示例中谈论的那样);
  • 假如 VALUE 同享不多,或许同享不会进步集群功能,或其它某种技能原因。
再次着重:假如一个 VALUE 的完成是可变的,那么就不能同享它。不论是否同享 VALUE OBJECT,在或许的情况下都要将它们规划为不行变的。

界说值方针并将它们指定为不行变是一条一般规矩,依据 FP 来进行范畴建模时触及的值方针都恪守这个一般规矩。

依据 FP 进行范畴建模的场景,便是经过范畴场景剖析发现范畴语义和 FP 编程范式之间的 Gap 最小。依据 FP 进行战术规划,一般触及的规划元素首要有三个:

  • 值方针:具有不行变性,表达了用户场景供给的数据语义;
  • 范畴服务:串联范畴模型的行为,封装范畴逻辑;
  • 应用服务:供给范畴的 API 给用户,最小依靠。

数形状的游戏

你是一名数学教师,在某次课间隔下课还有十分钟时,你决定做数形状的游戏,所以在黑板上画了一个图:


  • 游戏一:数数图中有几个三角形?
  • 游戏二:数数图中有几个四边形?

许多同学开端边比画边数,不论是三角形仍是四边形,每个人的数法不尽相同,大多数同学数的成果都少于实践的,也有一部分同学数的多于实践的,最终数对的同学其实很少。但假如让计算机来数的话,就必须将数三角形或四边形的办法描绘成计算机能够履行的形式化算法。这种描绘办法能够有许多种,而咱们期望找到一个笼统层次高且十分贴合范畴的描绘,以便下降后续的保护本钱。

1. 数三角形

范畴建模



咱们考虑一下:什么是三角形?

假如没有记错的话,咱们在小学就学过:三角形便是三个点,两两相连,可是三个点不在同一条线上。

咱们针对数三角形的游戏进行范畴建模,中心便是处理下面几个问题:

  • 上下文中的值方针都有哪些?
  • 三角形的三个点 (a,b,c) 都有哪些?
  • 计算机怎样知道两个点之间是否有连线,三个点是否在同一条线上?

值方针

咱们很简略想到两个值方针,即点和线,咱们经过 Golang 来简略表达:

type Point = byte
type Line = string

三角形有三个点:

[]Point{a, b, c}

三角形有三条边,即三条线:

[]Line{"ab", "bc", "ac"}

咱们能够把一条线看作该线上一切点的调集,下面给出 Points 的界说:

type Points = string

而关于点的调集,并不能判定调会集的一切点都在同一条线上,所以 Points 的语义比 Line 的更广泛。

依据上面的界说,教师在黑板上画的图形就能够表达为:

 points := "abcdefghijk"
lines := []Line{"abh", "acgi", "adfj", "aek", "bcde", "hgfe", "hijk"}

(x, y, z) 的无序戴志聪调集 (x, y, z) 的 Golang 言语形状是 []Point{x, y, z},能够将它转化成 Points 类型:

 xyz := Points([]Point{x, y, z})

咱们现已知道点的调集 points,那么 xyz 的调集简略来讲便是从 poi陈廷敬nts 的调会集取三个点的无序子集,每个子集的类型是个 Points,一切子集的类型便是 []Points,能够简略用 subset(points, 3) 来描绘。不过为了通用,咱们直接用 subset(points, n) : (Points, int) -> []Points 来建模:

subset(points, n) when len(points) < n -> []
subset(points, n) when len(points) == n -> [points]
subset(points, n) when n == 1 -> [points[0], points[1], ..., points[len(points) - 1]]
subset(points老树画画打油诗全集, n) ->
firsts = subset(points[1:], n - 1) with points[0]
lasts = subset(points[1:], n)
firsts append lasts

connected 和 insameline

咱们现已知道线的调集 lines,方针是要知道点与点之间联系:2 个点的联系 connected,3 个点的联系 in_same_line,等价于 2 个点或 3 个点组成的调集 points 是否为 lines 中任一元素的子集,无妨用 belong(points, lines) : (Points, []Line) -> bool 来表明这种联系,而 belong 现已是一个原子语义。

咱们下面用 belong 描绘一下 connected(x, y) : (Po北京移动,依据柔性制作的DDD战术规划实践-安博电竞APP下载ios-anggame安博电竞appint, Point) -> bool 和 in_same_line(x, y, z) : (Point, Point, Point) -> boo小丑的眼泪经典语句l:

connected(x, y) ->
belong(xy, lines)
in_same_line(x, y, z) ->
belong(xyz, lines)

范畴模型的树立

三角形的范畴模型咱们能够用形式化的办法表达如下:

is_triangle(a, b, c) ->
connected(a, b) and
connected(b, c) and
connected(a, c) and
not(in_same_line(a, b, c))举世黑卡

用代码表达范畴模型

model 代码完成

IsTriangle 函数的完成代码表达了范畴模型:

func IsTriangle(points Points, lines []Line) bool {
connected := hasConnected(lines)
in_same_line := inSameLine(lines)
a := points[0]
b := points[1]
c := p乳酸菌素片的效果oints[2]
return connected(a, b) &&
connected(b, c) &&
connected(a, c) &&
not(in_same_line(a, b, c))
}

关于 hasConnected, inSameLine 和 not 的函数完成,请参阅:

https://github.com/agiledragon/ddd-sample-in-golang/blob/master/counting-shapes/domain/model/set.go

domain service 代码完成

在范畴服务中,先从点的调会集获取 3 个点的一切无序子集,然后遍历所indeed有无序子集:判别无序子会集的三个点是否构成了三角形,假如是,则加到 matches 切片中:

func CountingTriangles(points model.Points, lines []model.Line) []model.Points {
sets := model.Subset(points, 3)
matches := make([]model.Points, 0)
for _, set := range sets {
if model.IsTriangle(set, lines) {
matches = append(matches, set)
}
}
return matches
}

app service 代码完成

应用服务托付范畴服务来数三角形,并回来三角形的个数:

func CountingTriangles(points strin寒战2g, lines []string) int {
return len(service.CountingTriangles(points, lines))
}

学生数三角形代码

咱们经过测验代码来模仿:

func TestCountingShapes(t *testing.T) {
points := "abcdefghijk"
lines := []string{"abh", "acgi", "adfj", "aek", "bcde", "hgfe", "hijk"}
Convey("TestCountingShapes", t, func() {aotm奥特曼动画片
Convey("counting triangles", func() {
num := service.CountingTriangles(points, lines)
So(num, ShouldEqual, 24)
})
})
}

2. 数四边形

范畴建模

咱们考虑一下:什么是四边形?


假如没有记错的话,咱们在初中就学过:由不在同一直线上的不穿插的四条线段顺次首尾相接围成的关闭的平面图形或立体图形叫四边形,由凸四边形和凹四边形组成。

有了三角形范畴模型的根底,下面三个问题现已处理:

  • 上下文中的值方针;
  • (a, b, c, d) 的无序调集,subset(points, n) when n = 4;
  • 恣意三个点不在同一直线上,即 not(in_same_line(a, b, c));

要形式化表达四边形的范畴模型,还要处理下面两个问题:

  • 多条线段顺次首尾相接构成一个环,咱们记作 ring_order_connected(a, b, c, d),计算机怎么知道?
  • 两条线段相交,咱们记作 cross_connected,计算机怎么知道?

ringorderconnected

咱们先形式化表达一下 ring_order_connected(x1, x2, ..., xn) 的意义:

ring_order_connected(x1, x2, ..., xn) ->
connected(x1, x2) and
connected(x2, x3) and
...
connected(xn-1, xn) and
connected(xn, x1)

咱们以四边形为例,将 ring_order_connected(a, b, c, d) 的意义图形化:


上面六个图中:

  • 前面两个图 ring_order_connected(a, b, c, d) 为真,且有序调集 (a, b, c, d) 是四边形栾树;
  • 中心两个图 ring_order_connected(a, b, c, d) 为真,而有序集北京移动,依据柔性制作的DDD战术规划实践-安博电竞APP下载ios-anggame安博电竞app合 (a, b, c, d) 有穿插线不是四边形;
  • 后边两个图 ring_order_connected(a, b, c, d) 为真,尽管有序调集 (a, b, c, d) 有穿插线不是四边形,但图五有序调集 (a, b, d, c) 是四边形,图六有序调集 (a, d, b, c) 是四边形。

从上面的剖析能够看出,四边形的辨认现已比三角形杂乱许多,(a, b, c, d) 不能简略的看作一个无序调集,而是一个有序调集。当 (a, b, c, d) 是一个四边形时,边 "ad" 和 边"bc" 没有穿插线,一起边 "ab" 和 边"cd" 没有穿插线。

已然 ring_order_connected 的输入是有序调集,而咱们经过 subset(points, n) 得到的是无序调集,那么无序调集怎么转化成有序调集?

关于 (x1, x2, ..., xn),当 x1 确认后,x2 有 n-1 个方位,x3 有 n-2 个方位,x2 有 2 个方位, xn 仅有一个方位,即当无序集的巨细为 n 时,有续集的个数为 (n-1)!。

当 n=4 时,咱们实例化一下:

set(x1, x2, x3, x4) ->
order_set(x1, x2, x3, x4) or
order_set(x1, x2, x4, x3) or
order_set(x1, x3, x2, x4) or
order_set(x1, x4, x2, x3) or
order_set(x1, x3, x4, x2) or
order_set(x1, x4, x3, x2)

cross_connected

当 cross_connected(ab, cd) 回来真时表明边 ab 和边 cd 是穿插线。咱们用肉眼能够清楚的看见两条边是床三否交是叉线,而计算机怎么知道呢?

经过调查教师在黑板上画的图,发现恣意两个穿插线的衔接点都有符号,所以恍然大悟。

关于 cross_connected(ab, cd) 来说,ab 边地点的线段和 cd 边地点的线段的两个“点的调集”中,假如 a 和 b 之间的点构成的“子集”与 c 和 d 之间的点构成的“子集”有交集,则阐明边 ab 和边 cd 是穿插线。

咱们把前面的两个图的穿插线的衔接点符号一下:


关于上面的任一张图,两个子集都是 [e],所以交集也是 [e],这便是说 (a, b, c, d) 表明的有序环衔接有穿插线。

更一般的,两个子集的或许性包含:

  • 某个子集为 北京移动,依据柔性制作的DDD战术规划实践-安博电竞APP下载ios-anggame安博电竞app[];
  • 两个子集都不为 [],但交集为 [],比方 [xyz] 与 [tmn];
  • 两个子集的交集至少有一个元素,比方 [xyz] 与 [z警犬实习日记mn]。

范畴模型的演进

有了 ring_order_connected 后,三角形三条边的两两相连就能够经过 ring_order_connected(a,花瓶 b, c) 来形式化表达,这时三角形的范畴模型就精练为:

is_triangle(a, b, c) ->
ring_order_connected(a, b, c) and
not(in_same_line(a, b, c))

四边形的范畴模型咱们用形式化的办法表达如下:

is_quadrangle(a, b, c, d) ->
ring_order_connected(a, b, c, d) &&
not(cross_connected(ab, cd)) &&
not(cross_connected(ad, bc)) &&
not(in_same_line(a, b, c)) &&
not(in_same_line(a, b, d)) &&
not(in_same_line(a, c, d)) &&
not(in_same_line(b, c, d))

:(a, b, c, d) 是有序调集,无序调集到有序调集的转化责任别离到范畴服务。

用代码表达范畴模型

model 代码完成

重构 IsTriangle 函数的完成精练范畴模型:

func IsTriangle(points Points, lines []Line) bool {
ring_order_connected := ringOrderConn柯震东终身禁演令ected(lines)
in_same_line := inSameLine(lines)
a := points[0]
b := points[1]
c := points[2]
return ring_order_connected(a, b, c) &&
not(in_same_line(a, b, c))
}

经过 IsQuadrangle 函数来演进范畴模型:

func IsQuadrangle(points Points, lines []Line) bool {
ring_order_connected := ringOrderConnected(lines)
cross_connected := hasCrossConnected(lines)
in_same_line := inSameLine(lines)
a := points[0]
b := points[1]
c := points[2]
d := points[3]
ab := Points([]Point{a, b})
cd := Points([]Point{c, d})
ad := Points([]Point{a, d})
bc := Points([]Point{b, c})
return ring_order_connected(a, b, c, d) &&
not(cross_connected北京移动,依据柔性制作的DDD战术规划实践-安博电竞APP下载ios-anggame安博电竞app(ab, cd)) &&
not(cross_connected(ad, bc)) &&
not(in_same_line(a, b, c)) &&
not(in_same_line(a, b, d)) &&
not(in_same_line(a, c, d)) &&
not(in_same_line(b, c, d))
}

关于 ringOrderConnected 和 hasCrossConnected 的完成,请参阅:

https://github.com/agiledragon/ddd-sample-in-golang/blob/master/counting-shapes/domain/model/set.go

domain service 代码完成

在范畴服务中,先获北京移动,依据柔性制作的DDD战术规划实践-安博电竞APP下载ios-anggame安博电竞app取 4 个点的一切无序子集,然后遍历一切无序子集:将每个无序子集转化成 6 个有序子集,遍历一切有序子集,判别有序子会集的 4 个点是否构成了四边形,假如是,则 append 到 matches 切片中,并退出有序子集的遍历,接着遍历无序子集:

func CountingQuadrangles(points model.Points, lines []model.Line) []model.Points {
sets := model.Subset(points, 4)
matches := make([]model李玲玉简历.Points, 0)
for _, set := range sets {
a := set[0]
b := set[1]
c := set[2]
d := set[3]
orderSets := []model.Points{
model.Points([]model.Point{a, b, c, d}),
model.Points([]model.Point{a, b, d, c}),
model.Points([]model.Point{a, c, b, d}),
model.Points([]model.Point{a, c, d, b}),
model.Points([]model.Point{a, d, b, c}),
model.Points([]model.Point{a, d, c, b}),
}
for _, orderSet := range orderSets {
if model.IsQuadrangle(orderSet, lines) {
matches = append(matches, orderSet)
break
}
}
}
return matches
}

app service 代码完成

应用服务托付范畴服务来数四边形,并回来四边形的个数:

func CountingQuadrangles(points string, lines []string) int {
return len(service.CountingQuadrangles(points, lines))
}

学生数四边形代码

咱们经过测验代码来模仿(数三角形和数四边形在同一个测验函数中):

func TestCountingShapes(t *testing.T) {
point星座查询表s := "abcdefghijk"
lines := []string{"abh", "acgi", "adfj", "aek", "b北京移动,依据柔性制作的DDD战术规划实践-安博电竞APP下载ios-anggame安博电竞appcde", "hgfe", "hijk"}
Convey("TestCountingShapes", t, func() {
Convey("counting triangles", func() {
num := service.CountingTriangles(points, lines)
So(num, ShouldEqual, 24)
})
Convey("counting quadrangles", func() {
num := service.CountingQuadrangles(points, lines)
So(num, ShouldEqual, 18)
})
})
}

小结

范畴模型是从范畴问题动身人为构建的一种面向范畴的指示性语义,挑选某种编程范式就选定了特定的构建根底。理论上不论挑选 OP、OO 仍是 FP 做为构建根底都是图灵齐备的,但在工程上需求考量哪种编程范式与范畴语义之间的 Gap 最小且保护本钱最低。人们现已在依据 OO 的范畴建模方面积累了很多的经历,而在依据 FP 的范畴建模方面的经历却比较匮乏。

本文首要共享依据 FP 的一次 DDD 战术规划实践。首要论述了依据 FP 进行范畴建模的场景是什么,然后以数形状的游戏为事例完好展现了战术规划实践的全进程,不只有范畴模型的树立,还有范畴模型的演进。在事例中,咱们用形式化的办法描绘范畴模型,用整齐的代码表达范畴模型,下降了软件的完成杂乱度,使得软件愈加贴合事务的实质。

附录

counting-shapes 源码链接:

https://github.com/agiledragon/ddd-sample-in-golang

第一个游戏:数数一共有多少个三角形?

24,概况如下:[abc abd abe acd ace ade aef aeg aeh afg afh agh ahi ahj ahk aij aik ajk beh ceg def ehk fhj ghi]

第二个游戏:数数一共有多少个四边形?

18,概况如下:[aceh adeg adeh afhk aghj aghk bcgh bcih bdfh bdjh bekh cdfg cdji ceki dekj efjk egik fgij]

如对此文有什么疑问 请在谈论区下方留言哦!