
引言
在第一篇文章中,我们重点介绍了指定实践的范围,可以将其应用到哪些项目中以及不应该将它们应用于哪些项目。
在本文中,我将简要概述DDD的基本原理,并分享我在DDD应用中的个人经验。 我们将更详细地讨论通信和结构化方法,并举例说明其实现方式。
在下一篇文章中,我将考虑到它们的实现,写下应用的设计模式的可能组合,最后,我将给出一个小型微服务的特定实现的示例。
DDD
回顾之前的定义:
域驱动设计(DDD)是一种软件开发方法,它通过将实现与不断发展的主要业务模型紧密联系在一起,从而全面满足需求。
用来描述构建复杂系统的实践的参考书是Eric Evans的《 领域驱动的定义》 (大蓝皮书)。 如果您阅读有关此主题的任何评论文章,则已经知道。 在实际使用DDD时,您将必须阅读它。 这不是最容易阅读的书:
DDD的规范来源是Eric Evans的书。 在软件文献中,这不是最容易阅读的书,但它却是其中一本可以充分回报巨额投资的书之一。
马丁·福勒(Martin Fowler):2014年1月15日
如果您滚动浏览这本书的内容,那么您似乎就不太习惯了。 但是地图会帮助我们。

我们今天将考虑的是这些做法。
本书涵盖的实践范围非常广泛。 在本书之外可以应用的实践范围更大。 在至少使用其中一部分之前,请确定您的目标。 我将以自己为例。
- 提高生产力。
- 编写易于理解的代码。
- 在软件开发级别进行扩展。
单一语言
软件开发很少会导致创建新的事物,通常,这是对现有事物的模拟。
模型是真实对象的表示,仅包含必要的属性和功能。
我们无法创建涵盖整个主题领域的软件产品。 只能复制其中将复制必要功能的那部分。
模型的一个很好的例子是地形图。 她是地形模型。 该地图不包含田野和河流的草地,仅反映了真实对象之间的相对位置。
要为每个人建立清晰明确的模型,您需要说相同的语言。 埃里克·埃文斯(Eric Evans)不仅告诉我们这一点,而且还告诉我们常识。 如果程序员使用他们的术语,并且使用他们自己的“ s语”,那么前者根本不会理解需要做什么。 在这种情况下,企业将无法实现开发一项或另一项“功能”的实际成本。 您听过多少次:“是的,这只是一个添加按钮”?
作为系统设计师,您的目标应该是使整个团队彼此之间有最大的了解。 如何实现呢? 开始讲话。 如果人们开始在任何亲密的小组中进行交流,那么他们将具有某些普遍接受的术语。 在不同的公司中,引入通用语言的过程可能会有所不同。 这可以是一个意志坚定的决定,也可以是一个民主程序。 可以明确指出该语言,而不会明确输入,在这种情况下,他们只是开始讲这种语言。 通用文档是介绍通用语言的一种好方法。
如何保留项目文件
- 业务与开发之间的任何沟通都应该改善您的模型。
- 会议结束后,以文档(Scrum工件)的形式记录结果,并向开发过程中的所有参与者显示此文档。
- 在文档中使用一种语言。
- 最重要的是:不要在文档上浪费时间。 您仍然必须编写代码,并且文档将被多次重写,从而花费大量资源。 不要在手机上长时间摆弄UML图表应用程序,而要使用餐巾纸,笔和相机。
- 文档需要纪律;您不能不时编写。
- 分隔文档:
- 代码中的注释-在代码中直接描述难以理解的时刻,保留
#ODO:
将代码合并到主代码时删除)。 在注释中表达您的意见,例如,在使用旧代码时必须使用一个或另一个拐杖。 - 项目根目录中的
README.md
项目的注释应包含技术信息:如何启动项目,如何运行测试等。 最好获得一张地图,其中包含所有项目以及它们在哪些服务器上运行。 单独记录所有接受的协议。 - 最重要的是知识库。 描述业务流程的文档的集合,这是您和企业都可以使用的文档的一部分。
- 编写文档的人的主要错误是冗余。 不要试图覆盖所有内容,仅传达一般含义。 该文档应补充您的项目,但不能以任何方式替代它。 不要写下所有模棱两可的术语。 如果一个定义使用两个以上的句子,则定义很差。
文档示例:
# . # : : - - email - ## : ### , email , 1 ( email ). ### . email . ### email email . , email. . ### , , . . ### email, , . 2 . ### . , , .
请注意,在此示例中,我们没有指定显式词典,但是固定了User , Authorization , Registration的概念。 撰写此类文档不会花费专家超过20分钟的时间。
对于不是该领域专家的人来说,编写文档的过程被认为是复杂的。 有必要将知识的收集与收集的知识的记录分开。 != + .
第四点说:“实际上,你所说的是宇宙的积累,就像弓的皮肤一样,它们彼此叠置,并逐渐彼此分离。”
-非常清楚地说明! -敬佩阿贝里德。 -太清楚了! “他们认为他们了解哲学家,因为他们非常了解洋葱是什么。”
阿伯丁故事,克里斯托夫·马丁·维兰德
有限的上下文和域
想象一下,我们担任过一家渐进式创业公司的设计师。 我们都喜欢冷却的比萨饼,喜欢用快递员诅咒他们,并在网站上填写表格数小时。 因此,我们提出了一个很棒的创业公司“四只乌龟和一只老鼠”:
- 有一个在其上注册比萨店并张贴其实际菜肴的网站。
- 这些比萨店没有自己的快递服务。
- 有些客户无法到达比萨店,但他们准备通过网站或移动应用程序下订单。
- 杀手'的特征:快递员不是专门雇用的人员,而是通过移动应用程序注册的普通人
- 快递员收到订单后,便会收到已完成工作的付款。
- 等待这样的快递员需要更长的时间,但是送货以及相应的比萨更便宜。
让我们回想一下上一章中描述的文档。 在那里,在我们的单本词典中,使用了注册一词 。 但是在这个项目中,我们有几个:
统一语言属于有限的上下文。 以上文档的域是“授权系统”。 让我们尝试为我们的启动分配域。
但是在开始之前,我们先来看一下有关什么是域以及什么是有限上下文的一些术语。
域(域)-解决特定问题的真实业务结构的表示。
例如:物流系统,付款系统,授权系统,订单管理系统。
该域分为多个子域,这些子域描述了较小的结构,例如:一篮子订单,用于构建路线的系统。
每个域的责任范围都有限-功能有限。
有界上下文-一组域限制,可帮助域仅将一项任务集中于最佳解决方案。
我喜欢将此术语表示为一种抽象。 域是一个圆。 受限制的上下文是一个圆圈。

即使使用DDD术语,也会分配内核。
核心(核心域)-最能代表您的业务特征的最重要域。
因此,项目“四只乌龟和一只老鼠”的领域:
与比萨店合作 (Pizzarias)
上下文 :b2b与比萨店有关的一切
子域名 :
与客户 (客户)合作
上下文 :b2c,与与披萨客户合作有关的一切
子域名 :
与快递员合作 (送货系统)
背景 :b2e,与与快递员合作有关的一切
子域名 :
订单系统
上下文 :内核。 允许您协调所有单个域,提供从接收订单到向用户发送披萨的完整周期。 它不是表演者,而是扮演指挥者的角色。
子域名 :
结算系统 (计费)
上下文 :包含所有财务交易。 它提供与处理中心的交互。
子域名 :
统计系统
上下文 :分析信息的收集和处理(而不是发布)。
子域名 :
管理系统 (管理面板)
上下文 :发布分析信息。 管理决策工具包。
根据域,让我们对其进行映射。
域映射(上下文映射)是一种图形工具,可让您描述各个域之间的关系。

该地图显示了域之间的链接。 该地图非常肤浅,但是对主题领域的了解却很少。 这是第一个草图,重写它会得到预期的结果。
地图上最重要的是,我们看到了域之间的连接。 这样的结构非常适合微服务架构:
微服务架构的主要原理:弱连接性和强粘附性。
山姆·纽曼(Sam Newman)-《 创建微服务》(Create Microservices)一书中给出了该原理,这是您为了开始实际使用本文中介绍的方法而必须阅读的第二本书。 含义:域应松散耦合,但内部应紧密相连。
这些术语的翻译来自官方的俄语翻译,可能无法很好地反映所传达的含义。 原始术语是:低耦合(连接性,接合,抓地力,接合),高内聚性(连接性,强度)。
域共享实施实践
我想分享我的个人经验-一系列明智的决定。 我不敦促您使用这些解决方案。 但是,如果您不知道从哪里开始,那么它们可能是一个不错的选择。 凭着个人经验,这些工具将进一步适合您的需求。
指导我们的主要原则:
- 解决方案的简单性。 使复杂的事情变得简单,而不是简单的复杂。
- 实用主义 您始终需要查看情况,并且在没有现有解决方案的情况下,开发新的解决方案。 尽量代表一切,但要避免教条主义。
- 代码!=文档。 代码是机器的说明,文档是人的说明。 无需混淆它们,一个接一个地发出。
- 实心
如何实现域名?
选择域作为单独的微服务非常方便。
微服务是实现一个域的逻辑的独立应用程序。
在DDD开发中,将微服务分配到单独的应用程序中的原理将受到限制。 这并不会否定服务分离的技术原理(如果这是由于需要确保高性能)。 但是上下文原则将是主导性和约束性的。
如何突出域之间的关系?
域之间的关系始终是一个API。 可能是RESTful json API,gRPC,AMPQ。 在本文的框架内,我们不会将一种协议与另一种协议进行比较,并强调它们的优缺点;每种协议都有自己的应用领域。 但是,让我们继续关注一般建议:
选择协议时要灵活,执行协议的一致性要严格。
为每对域分别选择一个协议,不要尝试在所有地方都使用http,您可能需要在某个地方使用异步队列,并且AMPQ的优势对您来说显而易见。 不要忽略这个机会,因为到处都有RESTful。
另一方面,如果要实现RESTful json,请使用一个数据结构标准。 您可以准备就绪,例如jsonapi或openapi。 如果由于某种原因,现成的解决方案不适合您,并且您认为可以开发,描述和使用它。 但是到处使用它,不要滋生“动物”标准。 如果您需要与他们对标准一无所知的外部系统进行通信,请编写一个微服务适配器。

如何实现子域?
作为微服务中的独立模块。
通过将逻辑放入单个微服务内的单独命名空间(名称空间)中,模块是子域的实现。
看起来像什么? 让我们来看一个例子。 我们记得,我们有一个Delivery System域-该域有三个子域:
- 快递注册
- 任务(任务)的发布
- 登记由快递员赚取的资金的提取申请(提取)
- 检查您的微服务是否正常工作,辅助技术工具(healt_checker)
想象一下所有这些以文件夹结构的形式:
$ tree --dirsfirst delivery_system delivery_system ├── app/ │ ├── health_checker/ │ │ └── endpoints.rb │ ├── registrations/ │ │ ├── entities/ │ │ ├── forms/ │ │ ├── repositories/ │ │ ├── interactor/ │ │ ├── services/ │ │ ├── validations/ │ │ ├── endpoints.rb │ │ └── helpers.rb │ ├── tasks │ │ ├── entities/ │ │ ├── queries/ │ │ ├── repositories/ │ │ ├── endpoints.rb │ │ └── helpers.rb │ └── withdrawals │ ├── entities/ │ ├── forms/ │ ├── repositories/ │ ├── interactor/ │ ├── services/ │ ├── validations/ │ ├── endpoints.rb │ └── helpers.rb ├── config/ ├── db/ ├── docs/ ├── lib/ │ ├── schemas/ │ └── values/ ├── public ├── specs ├── config.ru ├── Gemfile ├── Gemfile.lock ├── Rakefile └── README.md
apps/
目录中的每个文件夹都实现一个或另一个子域,每个域中都有多种模式: entities
, forms
, services
等。我们将在以后的文章中详细讨论每种应用的模式。
每个这样的模式都在相应的名称空间(名称空间)中实现。 例如,用于创建向快递公司付款的应用程序的表格:
module Withdrawal
如何实现子域之间的通信?
让我们看一个具体的例子。 我们有一个快递帐户: Registrations::Entities::Account
。 它指的是Registrations
子域-因为我们认为此域不是注册过程,而是帐户表和注册簿,如业务文档中所示。
我们有两个执行流程 ,可以访问该帐户。
- 创建一个帐户(注册)
- 申请提取快递员赚取的资金(Wihtdrawal)
如我们所见,这两个过程属于不同的子域-注册和Wihtdrawal。
module Registrations module Serivices class CreateAccount def call account = Entities::Account.new end end end end module Withdrwals module Serivices class CreateOrder def call account = Registrations::Entities::Account.new end end end end
在第一种情况下,将通过对Entities::Account
的调用来实现对该类的调用。 在第二种情况下,通过显式调用Registrations::Entities::Account
。 即 如果我们明确指定一个子域,则该类来自另一个子域,因此我们清楚地指出了连接。
如果该类未明确应用于任何子域,则将其移至lib/
文件夹是有意义的。 通常,这些是实现“ ValueObject”模式的类。 在以下文章之一中,我们将更详细地研究这种模式。
通过模型执行。
引用埃里克·埃文斯的话:
如果程序的体系结构或其至少某些中心部分与领域模型的结构不符,则该模型实际上是无用的,还应质疑程序的正确操作。 同时,很难理解软件体系结构中模型和功能之间的关系太复杂,并且在实践中,随着体系结构的变化,很难维护它们。
让我们回想一下本文开头已经提到的一个好的模型的例子-地形图。 我们的目标是能够快速找到两个定居点之间的距离。 我们可以使用参考表来显示城市之间的两个点。 我们可以使用该卡。 那里和那里我们大约在同一时间获得相同的结果。 但是地图更紧凑,它更准确地显示主题区域,更通用。 地图作为模型具有极高的表现力。 而且,如果我们在此任务的框架内考虑它,那么在地图上测量距离比在其本身反映的领土上更方便。 反映主题区域的模型在某些属性上可能会超越它。 真的很棒。
模型的实施始终是一个具有不可预测结果的创新过程。 代码的质量不是其性能,也不是其复杂性,而是简单性和表达性。 通过不断的重构来改进它,使其变得灵活并切断所有不必要的东西。 将负责模型的业务逻辑的层与需要归因于技术实施的层分开。 我们将如何做到这一点将在后面描述。