<?xml version='1.0' encoding='UTF-8'?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>领域驱动设计 · Tommy Cheese</title>
    <link>https://tommycheese.github.io/tags/%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1/</link>
    <description>Tommy Cheese 的个人博客，记录软件工程、人工智能与学习实践。</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-CN</language>
    <lastBuildDate>Fri, 26 Jul 2024 19:49:45 +0800</lastBuildDate>
    <atom:link href="https://tommycheese.github.io/tags/%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1/index.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>领域驱动设计初识</title>
      <link>https://tommycheese.github.io/blogs/ddd%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%E5%88%9D%E8%AF%86/</link>
      <pubDate>Fri, 26 Jul 2024 19:49:45 +0800</pubDate>
      <guid>https://tommycheese.github.io/blogs/ddd%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%E5%88%9D%E8%AF%86/</guid>
      <description>&lt;h1 id="领域驱动设计概述"&gt;领域驱动设计概述&lt;/h1&gt;
&lt;p&gt;领域驱动设计（DDD, Domain-Driven Design）是一种模型驱动设计的方法，它通过领域模型捕捉领域知识，使用领域模型构造&lt;strong&gt;更易维护&lt;/strong&gt;的软件.&lt;/p&gt;
&lt;p&gt;DDD的设计过程分为战略设计与战术设计，其中战略设计面向领域、子域以及限界上下文的设计，而战术设计面向实体、值对象、领域事件等设计，关系如下：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tommycheese.github.io/blogimages/DDD1.png" alt="img"&gt;&lt;/p&gt;
&lt;h2 id="战略设计阶段相关概念"&gt;战略设计阶段相关概念&lt;/h2&gt;
&lt;h3 id="领域"&gt;领域&lt;/h3&gt;
&lt;p&gt;领域是系统要解决问题的领域，如商品信息管理就可以是系统要解决问题的领域.&lt;/p&gt;
&lt;h3 id="子域"&gt;子域&lt;/h3&gt;
&lt;p&gt;根据使用语言的不同可以将领域划分为不同的子域：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心域：决定产品核心竞争力的子域，是最为重要、业务最核心、个性的部分；&lt;/li&gt;
&lt;li&gt;通用域：被多个子域使用的通用功能子域，比如用到的通用系统，例如认证、权限等等，这类应用没有企业特点限制，不需要做太多的定制化；&lt;/li&gt;
&lt;li&gt;支撑域：不包含核心功能与通用功能的子域，具有企业特性，但不具有通用性，例如数据代码类的数据字典等系统；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="限界上下文"&gt;限界上下文&lt;/h3&gt;
&lt;p&gt;限界上下文，即限定使用不同模型来解决不同问题所产生的不同区域.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;限界上下文是一个子域或者多个子域的集合&lt;/strong&gt;，&lt;strong&gt;要保证一个限界上下文须支持一个完整的业务流程&lt;/strong&gt;，&lt;strong&gt;保证这个业务流程所涉及的领域都在一个限界上下文中&lt;/strong&gt;.限界上下文是微服务拆分的依据，即每个限界上下文对应一个微服务.&lt;/p&gt;
&lt;p&gt;那么为什么要进行限界上下文呢？这是因为&lt;strong&gt;上下文&lt;/strong&gt;的存在导致编码十分困难，举个例子说明：&lt;/p&gt;
&lt;p&gt;例如在招聘域我们可能会使用“平台”来表明来自于哪个学校、机构；在跳水域我们使用“平台”来表明选手从那个跳台跳水；而在铁路交通运输领域，我们可能会使用“平台”来描述铁路站台……在某一天的某一个机会，跳水选手、HR和乘务员聚到了一起，并且他们都不了解对方的身份，如果此时HR问跳水选手说你来自哪个平台……&lt;/p&gt;
&lt;p&gt;看到了吧，同样是使用“平台”这个词语，但三个人在&lt;strong&gt;没有约定&lt;/strong&gt;的情况下可能对于词语的理解不尽相同，解决这个问题的办法就是把跳水域、招聘域和交通域隔离，分别定义域内的术语，在一个域内讨论就可以最大可能的避免歧义，这就是约定.这也是为什么最好把业务所涉及的领域定义在一个限界上下文中：减少歧义.&lt;/p&gt;
&lt;p&gt;在软件系统的设计中同样会遇到类似的问题，如果你负责设计一个足够大的计算机软件产品，产品涉及到跳水服务、招聘服务与乘车服务，系统没有对服务做任何切分，你在跳水有关业务逻辑中使用platform数据结构描述跳台、在招聘业务中使用platform代表平台、在交通业务中使用platform代表站台，当这三个业务不可避免的交汇到一起时，灾难就发生了，面对满屏幕不同语义但几乎同名的变量，我们能怎么办？&lt;/p&gt;
&lt;p&gt;显然，限定某一个领域术语的细节，我们就可以在领域中畅通无阻的使用这个术语了，这就是限界上下文的重要作用.&lt;/p&gt;
&lt;h2 id="战术设计阶段相关概念"&gt;战术设计阶段相关概念&lt;/h2&gt;
&lt;h3 id="值对象和实体"&gt;值对象和实体&lt;/h3&gt;
&lt;p&gt;首先了解一下值对象和实体.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;值对象是通过属性值来识别的对象，即如果两个值对象的内部值都是相同的，那么我们就认为这两个值对象是相同的.显然，值对象的属性不可变.&lt;/li&gt;
&lt;li&gt;实体是拥有唯一标识和状态，且具有生命周期的业务对象.实体的属性是可变的，如果两个实体的属性是完全相同的，我们也不认为他们是相同的实体，只有他们两个的标识（如ID）是相同的才会被认为是相同的实体.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;举例区分值对象和实体：&lt;/p&gt;
&lt;p&gt;假如现在我们有一个白色值对象Color white{R:255, G:255, B:255}和一个轮胎实体tire{Air:&lt;em&gt;,Size:&lt;/em&gt;}：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可变性：白色中的每一个属性都是不可变的，因为一旦变化此对象就不再代表”白色“；而轮胎中的气压、尺寸等值可以改变，因为了即便这些参数发生了变化它仍然是轮胎；&lt;/li&gt;
&lt;li&gt;可比较性：正是由于值对象的值不可变性赋予了其相等的规则，两个白色对象的值一定会是相同的，因此如果两个值对象的内部值都是相同的，那么我们就认为这两个值对象是相同的，实体则由于其属性的可变性而丧失了这个特点.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于实体而言，其形态一般有四种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;失血模型：模型仅仅包含数据的定义和getter/setter方法，业务逻辑和应用逻辑都放到服务层中.这种类在Java中叫POJO.&lt;/li&gt;
&lt;li&gt;贫血模型：贫血模型中包含了一些业务逻辑，但不包含依赖持久层的业务逻辑.这部分依赖于持久层的业务逻辑将会放到服务层中.&lt;/li&gt;
&lt;li&gt;充血模型：充血模型中包含了所有的业务逻辑，包括依赖于持久层的业务逻辑.&lt;/li&gt;
&lt;li&gt;胀血模型：胀血模型就是把和业务逻辑不相关的其他应用逻辑（如授权、事务等）全部都放到领域模型中.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;资源库Repo&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Repo是针对Entity设计的存储操作，Repo中的操作应该尽可能低级、命名要简短，并且不能定义太多.Java中的MyBatis Mapper可以看作是Repo的一种实现.&lt;/p&gt;
&lt;h3 id="聚合与聚合根"&gt;聚合与聚合根&lt;/h3&gt;
&lt;p&gt;聚合是一种更大范围的封装，把一组有相同生命周期、在业务上不可分隔的实体和值对象放在一起考虑，只有聚合根可以对外暴露引用，聚合也是一种内聚性的表现.聚合根之间也可相互调用，聚合根抽象出来一般名字为名词.&lt;/p&gt;
&lt;p&gt;聚合根通过以下几种手段实现封装：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必须通过操作聚合根来实现操作整个聚合，外部操作不允许直接操作聚合中的元素.比如紫色（外观）+轮胎+钢架+…=汽车，我们开车时不能也不会去单独操作轮胎、方向盘、外观…，相反，我们通过操作汽车这个聚合根来间接操作其他实体/值对象.&lt;/li&gt;
&lt;li&gt;聚合定义了一组&lt;strong&gt;边界&lt;/strong&gt;，边界内所有的组件必须对业务逻辑有效；&lt;/li&gt;
&lt;li&gt;必须在一个原子性的事务中操作聚合，否则可能会出现异常，也就是说聚合是操作的单元，从仓库取出聚合、操作完成后放回是一个原子性的操作；如何理解原子性？加入汽车聚合根有轮子实体、钢/铝/碳架实体，你不能拆掉汽车的一个轮子检修完后不装上.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="领域事件domain-events"&gt;领域事件Domain Events&lt;/h3&gt;
&lt;p&gt;当实体的属性发生变化时就会产生领域事件，领域事件是领域专家认为重要的事情.领域事件是发生在领域中且值得注意的事件.而领域事件通常意味着领域对象状态的改变.&lt;strong&gt;领域事件在系统中起到了传递消息、触发其他动作的作用，是解耦领域模型的重要手段之一&lt;/strong&gt;.我们往往利用&lt;strong&gt;消息队列&lt;/strong&gt;来传递领域事件，这样所有订阅此消息的子域都会进行自己内部的响应操作.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;消息总线也是一种实现方法👋.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如轮胎实体气压发生变化时，会释放领域leaked漏气事件，此漏气事件传递了“轮胎漏气”这个消息，从而引发一系列其他的响应，如动能系统变化、方向盘手感变化等等，领域事件通常会使用过去式命名来说明事件已经发生，不可撤销.&lt;/p&gt;
&lt;h3 id="领域服务domain-service"&gt;领域服务Domain Service&lt;/h3&gt;
&lt;p&gt;有些领域中的动作看上去&lt;strong&gt;并不属于任何对象&lt;/strong&gt;.它们代表了领域中的一个重要的行为，不能忽略它们或者简单地把它们合并到某个实体或者值对象中.当这样的行为从领域中被识别出来时，推荐的实践方式是将它声明成一个服务，这个服务就是领域服务.&lt;/p&gt;
&lt;p&gt;领域服务不是微服务中的“服务”概念.服务和聚合根概念有些相近，他们都可以操作多个实体，但是二者理念和作用不同，&lt;strong&gt;聚合根是对实体的组合，而服务是用于同步多个实体的状态，比如将某个item实体加入到list实体&lt;/strong&gt;（比如把mail投递到inbox）.所以Servie一般抽象出来都是动词.&lt;/p&gt;
&lt;h2 id="ddd领域建模设计领域模型"&gt;DDD领域建模（设计领域模型）&lt;/h2&gt;
&lt;p&gt;DDD领域建模一般步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;根据需求划分出初步的子域和限界上下文，以及上下文之间的关系；&lt;/li&gt;
&lt;li&gt;进一步分析每个上下文内部，识别出哪些是实体，哪些是值对象，并确定实体需要使用哪种代码形态：失血、贫血、充血、胀血；&lt;/li&gt;
&lt;li&gt;对实体、值对象进行关联和聚合，划分出聚合的范畴和聚合根；&lt;/li&gt;
&lt;li&gt;为聚合根设计资源库repo，并思考实体或值对象的创建方式；&lt;/li&gt;
&lt;li&gt;在工程中实践领域模型，并在实践中检验模型的合理性，倒推模型中不足的地方并重构.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这就是DDD采用两阶段设计原则——先进行战略设计、随后进行战术设计。&lt;/p&gt;
&lt;p&gt;在实践中，建议实体采用失血模型（实体方法只包括setter/getter）或者贫血模型（包含不涉及数据库操作的简单逻辑，如属性合法性校验）；&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实际上这是DDD建模方式之一，是自顶向下的设计方法，还存在一种方式是使用自下而上的方法，即先确定领域模型——实体、值对象等，再确定子域、限界上下文…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;（本节完）&lt;/p&gt;

          </description>
    <category>软件架构</category><category>领域驱动设计</category></item>
    </channel>
</rss>
