[

](gennyallcroft.medium.com/?source=pos…)
6月17日
-
10分钟阅读
[
拯救
在C#中践行领域驱动设计
在用领域驱动设计概念构建一个新产品时,需要考虑的关键战略和战术问题

我刚刚读完Vlad Khononov的《学习领域驱动设计》。这是一本相当短的书(约300页),旨在教给初学者关于领域驱动设计的所有知识。
我想通过将它们付诸实践来检查我对书中概念的理解。我希望你通过阅读这本书也能学到一些东西。这就是我的学习情况...

什么是领域驱动设计?
领域驱动设计的理念是将你所构建的业务需求作为架构的基础。
要求你构建产品的业务团队是 "领域专家",他们工作的业务领域是 "领域"。
有很多关于使用 "泛在语言 "的讨论,这意味着你不要使用非技术人员可能不理解的编码词汇,而是用业务团队描述产品的词汇来命名你的类、方法和变量,并讨论这些东西。

DDD的理念是用日常的英语来写代码。
关于如何实现领域驱动设计,书中有很多不同的技术概念,我将在本文中尝试解释其中的一些概念。我将通过构建一个假想产品背后的思维过程作为例子来进行解释。
我将建立什么?
我对产品的想法是让你能与其他人一起进行体育活动。我打算把它叫做 "运动连接"。
以下是我刚编出来的一些要求。
- 我希望用户能够给其他有兴趣从事相同运动的人发信息,而且他们的水平相似。
- 我希望用户能够在完成后对与对方的游戏体验进行评价。
- 我希望用户能够登录和退出。
- 我希望体育教练能够看到他们所在地区的运动项目的球员评级和标准水平,这样他们就可以为球队侦察他们,并在应用程序中给他们留言。
- 我希望运动队能够管理赛程和训练时间表,以及为球队挑选的每场比赛的球员,并向球队的球员发送通知。
- 我希望联赛中的球队能够根据过去的表现进行评级,并提供赛程预测。
战略决策
在你开始建立任何东西之前,首先要考虑的是你有什么 "类型 "的不同子域。这是一个领域驱动的设计概念。这里有一点解释。
域名和子域名
我的产品的领域是一种体育运动员市场和运动队管理。对于领域驱动的设计,我们真正感兴趣的是子域是什么。
子域是领域的不同部分,它们被分为不同的类型。
1.核心子域
核心子域 "是最重要的子域类型,是使企业赚钱并从竞争对手中脱颖而出的原因。在我上面的例子中,我认为核心子域是。
- 通过分析,找出附近的其他球员,他们对相同的运动感兴趣,并具有类似的标准
- 计算出一个体育运动员的评级
- 体育教练可以为球队挑选球员,并投入他们的位置
- 对运动队进行评级
- 体育联赛中的得分预测
- 一个真正用户友好的网站,有一个很好的设计
这些都是相对复杂的逻辑位,可能会根据业务需求而经常改变需求。核心子域是应该由你最好的开发人员在内部建立的东西。在这种情况下,由我在我的卧室里完成。

2.通用子域
通用子域 "是子域的下一个类型。它们通常很复杂,很难实现,但不提供任何竞争优势。这类子域通常是向第三方购买的,因为自己建立这些子域是浪费时间。
在我的例子中,我认为一般的子域是。
- 让用户相互联系的信息传递系统
- 登录和退出系统
3.支持性子域
最后一种类型的子域是 "支持性子域"。这是不提供任何竞争优势的东西,但真的很容易实现,例如,从CRUD接口输入和获取数据。对于这个产品,我的支持性子域是。
- 输入和存储用户的个人数据
- 输入并存储用户给对方的评分
- 团队活动和训练计划
好的,太好了!我们已经想好了我们的子域。我们已经弄清楚了我们的子域。我们必须分别思考这些问题,以及它们如何融入我们的解决方案。现在让我们来学习一下有界上下文。
有界限的上下文
有界限的上下文是领域驱动设计的另一个概念。我对它的理解是,它是一种范围,在这个范围内,"普遍的语言"(即领域的语言)是适用的。
如果两个不同的业务领域用同一个词来表达不同的意思,那么这就是一个很好的迹象,说明这是两个不同的有界的语境。
在我的例子中,我可能会发现,在个人球员用来找人玩的功能背景下,球员这个词与体育教练在管理球队时使用的球员这个词有着完全不同的含义。

因此,为个人球员制定一个有边界的上下文,为团队管理制定另一个有边界的上下文,可能是明智的。
一般来说,有界环境越小越好,因为它更灵活。不过,这也是有代价的,因为把上下文做得太小会导致整合时出现不必要的复杂性。
在实践中,只有一个团队的开发人员应该在一个约束的上下文上工作(但同一个团队可能会在一个以上的约束上下文上工作)。
这就是我们将从 "战略 "设计的角度考虑的所有东西。现在我们来看看 "战术性 "设计。
战术设计
是时候进入我们的代码将如何呈现的细枝末节了。
在我们上面定义的每个子域中,我们应该考虑一下我们要如何设计架构。每个子域都会根据其复杂性有自己的架构设计。
书中有一个流程图,有助于说明你什么时候可能要使用每种类型的架构。
如果你对术语感到困惑,端口和适配器架构也被称为清洁架构。
通用子域
我们的想法是将子域的架构与它的复杂性相匹配--对于具有基本数据访问功能的通用子域,用一个简单的 "活动记录 "类型的架构来设计它可能是有意义的。
这将允许你执行CRUD操作,但不必担心编写SQL脚本等问题。
核心子域
对于核心子域--产品的多汁部分--业务逻辑可能会更加复杂。你可以从流程图中看到,使用干净的架构或CQRS是一个好主意。
在这篇文章中,我将尝试用领域模型来构建一些东西,这样我就可以实践领域驱动的设计原则了(当然!)。领域模型可以很好地适用于清洁架构,所以我将继续使用它,因为这是我熟悉的东西。
你可以从流程图中看到,我所读的书也涵盖了事件源和事件源领域模型......但由于这是我第一次尝试使用这些概念,我将保持简单,使用一个更直接的领域模型。我将把事件源和CQRS的实践留到以后再做!
领域模型
在我的一个核心子域中,我想将从事相同运动且水平相似的球员相互匹配。
我将创建一个球员模型。在思考领域模型时,有几件重要的事情需要考虑。

照片:Jason StrullonUnsplash
类型
价值类型
值类型是指如果它的一个属性被改变就会变成另一个东西。
玩家模型中的一个例子可能是地址类型。它将有第一行、第二行、城市和邮政编码。如果这些东西中的任何一个发生变化,那么这个地址就会变成一个新的不同的地址。
实体类型
实体类型是你期望随着时间的推移而改变的东西,需要某种ID,以便能够将其与其他对象区分开来。
Player 模型本身将是一个实体类型,因为玩家的数据可能会随着时间而改变。球员将可以通过一个ID号来识别。
作为一般规则,如果你能用价值类型来分解实体类型,那么你就应该这样做。这里是我的球员模型的一个粗略的初稿。
球员类是一个实体,但其属性都是价值类型。如果任何一个价值类型的属性发生了变化,那么它就会给出该对象的一个新版本。
聚合(Aggregates
聚合是领域驱动设计的另一个概念。它可以说是最重要的一个,也是最难做好的一个。
它是单个或一组实体模型,它们连接在一起,其行为相互影响。
聚合体的一个真正重要的部分是,逻辑必须验证所有进入的变化,并确保这些变化符合实体的业务规则。
实体外部的一切都只允许读取聚合体的状态。这就是为什么有一个私有的setter。
聚合体的状态只能通过执行聚合体的公共接口的方法来改变。
在这里,我调整了我的Player 模型,使其成为一个Player 聚合。
所有的属性都有一个私有的setter。我添加了一些方法来比较球员,如果他们是 "匹配 "的,就把他们添加到列表中,还有一些方法来更新球员的评级和地址。
这些 "命令"(在领域驱动设计中被称为 "命令")通过聚合的公共接口被调用。
清洁的架构
如果你不熟悉清洁架构,我之前在这里 用C#语言建立了一个小型的例子项目-- 快速阅读一下可能会有帮助,因为我不会在这里过多地讨论清洁架构的细节。
领域层
这一层有解决方案的所有业务逻辑。在我的领域层中,我将有我之前向你展示的Player 聚合。
用例
为了这篇文章的目的,我只创建了几个将被我的应用程序使用的用例。
- 添加一个新的球员;和
- 将一名球员与另一名球员进行比较,如果是匹配的,则将其添加到球员匹配列表中。
你可以看到,我的用例类中的代码实现了播放器集合中的命令。
Player 模型完全负责更新Player 实体的状态。由于私有设置器的存在,播放器不能被实体之外的逻辑所修改。
这里是添加播放器的用例代码。
这段代码在Player 集合中调用了Create 命令。然后它将Player 添加到Player 资源库中。
这里是比较播放器用例的代码。
这段代码使用Player 集合中的ComparePlayer 命令,如果满足匹配条件,就用新的球员更新PlayerMatches 列表。
演示层
我还没有为这个例子建立一个表现层,但简洁架构的好处是,任何一种表现类型都可以被使用,而不会干扰我们到目前为止所写的代码中的任何核心业务逻辑。
最终,对于这个应用程序,我们希望为网络或移动端建立一个时髦的用户界面......但在这个阶段我们不需要对此做出任何决定,因为业务逻辑完全独立于表现层的选择。
结论
所以,你已经拥有了它!这是一篇相当长但很好的文章,讲述了在用领域驱动的设计概念构建一个新产品时需要考虑的关键战略和战术问题。
我已经告诉你如何计算出不同类型的子域,以及每种类型应该使用的架构类型。
我还试着用领域模型和简洁的架构制作了一个非常简单的产品核心子域的精简版。
我希望你觉得这篇文章很有用,可以给你一些领域驱动设计的背景知识,并看到它在实践中可能出现的一个例子