程序员技术精进:业务分析与设计,领域驱动设计与模型实践

简介: 领域驱动设计是一种将实现连接到持续进化的模型中来满足复杂需求的软件开发方法,通过将软件的相关部分连接到不断发展的模型中来简化复杂应用程序的创建流程。领域驱动设计侧重于以下三个核心原则。

领域驱动设计

领域驱动设计是一种将实现连接到持续进化的模型中来满足复杂需求的软件开发方法,通过将软件的相关部分连接到不断发展的模型中来简化复杂应用程序的创建流程。领域驱动设计侧重于以下三个核心原则。

◎ 把项目的重点放在核心域(Core Domain)和域逻辑上。

◎ 把复杂的设计放在有界域(Bounded Context)的模型上。

◎ 和领域专家不断协作完善应用模型来解决特定领域的问题。

所以,在领域驱动设计中非常重要的一个概念就是领域,而领域又可以划分为问题域和问题解决域。领域中各概念之间的关系如图3.28所示。

图3.28

领域指一个组织在某个特定的范围内应用特定的方式进行特定的活动。例如,一个公司通常会先确定一个市场,然后在这个市场上研发产品和提供服务。

领域驱动设计就是先将在领域中涉及的数据、流程、商业规则等都弄明白,然后以面向对象的观点为其建立一个领域模型,再选用合适的软件技术实现这个模型。在领域中涉及的数据、流程、商业规则等就是我们常说的领域问题(问题域),对于大的问题又会按功能或重要程度划分为不同的子问题(子域),采用分而治之的思想;领域模型及使用合适的软件技术去实现个模型就是我们常说的问题解决方案(问题解决域)。图 3.29展示了领域和各子域的关系。

图3.29

在将限界上下文划分好之后,还要确定限界上下文之间的关系。

通过限界上下文的映射关系,能够明确限界上下文之间的耦合关系。

这里主要关注限界上下文的内部交互和外部交互的耦合度,以最大限度地减少耦合度的层级。限界上下文之间的关系有以下几种。

◎ 合作关系(Partnership):两个合作的上下文会相互影响和制约。

◎ 共享内核(Shared Kernel):两个上下文都依赖部分共享的模型。

◎ 客户方-供应方开发(Customer-Supplier Development):上下文之间有组织的上下游依赖。◎ 遵奉者(Conformist):下游上下文只能盲从上游上下文。

◎ 防腐层(Anti-Corruption Layer):上下文之间通过适配和转换进行交互。

◎ 开放主机服务(Open Host Service):指定一套明确的协议,方便其他上下文访问。

◎ 发布语言(Published Language):在一般情况下和开放主机服务一起使用,用来定义开放主机的协议。

◎ 大泥球(Big Ball of Mud):混杂在一起的上下文关系,边界不清晰。

◎ 无关系(Separate Way):两个完全没有联系的上下文。

耦合度就是某模块与其他模块之间关联、感知和依赖的程度,是衡量模块独立性的一个指标,也是系统分析与设计中的重要指标。

接下来详细介绍领域驱动设计中的一些核心要素,如下所述。

(1)分层架构(Layered Architecture):分层架构的原则之一是只能向下依赖,不能向上依赖。分层架构有严格分层架构和松散分层架构两种:在严格分层架构中,当前层只能依赖其直接下方的层;

在松散分层架构中,当前层可以依赖其下方的任意层。领域驱动设计的分层架构图如图3.20所示。

(2)应用层(Application Layer):负责将展示层的请求转发到领域层,将领域层的执行结果返回给展示层,并根据业务对象调用相应的应用服务。它不包含业务逻辑,相对来说是较“薄”的一层。

在该层除了可以定义应用服务,还可以进行安全认证、权限校验、持久化事务控制、调用外部系统或者向其他系统发送事件消息等。另外,应用层是展示层与领域层的桥梁,展示层使用视图模型进行界面展示,与应用层通过数据传输对象进行数据交互,从而达到展示层与

领域对象解耦的目的。(3)领域层(Domain Layer):负责表达业务的概念,实现全部的业务逻辑并且通过各种校验手段保证业务的正确性,是核心部分。

业务逻辑包括业务的流程、策略、规则、状态及完整性约束等,所以领域层是较“胖”的一层。

(4)实体(Entity):实体是一个不由自身属性定义而由它自身身份定义的对象,具有状态和行为两个属性。实体对象具有唯一性和可变性,唯一性由唯一的身份标识来确定,可变性则反映了实体本身的状态和行为。

(5)值对象(Entity Object):只包含元素属性的不可变对象。值对象用于描述和度量事物,创建、使用、优化、测试和维护起来非常容易。比如对于某个地址(Address)对象,不通过唯一的身份标识 ID 来决定它的唯一性,只通过固定不变的概念来表示一个具体的地址即可。

(6)服务(Service):服务分为应用服务和领域服务,强调与其他对象的关系,只定义了可以为客户做什么,而不应该替代实体(Entity)和值对象的所有行为,也就是常说的充血模型。

◎ 应用服务:是用来表达用户故事(User Story)和用例的主要手段。应用层通过接口来展示应用服务的功能列表,应用服务负责服务编排和请求转发,同时将需要实现的功能通过领域对象来实现。应用服务只负责处理业务用例的执行顺序和结果拼装。这样可以很好地隐藏领域层的复杂性和内部实现原理。

◎ 领域服务:表示一种无状态的操作,用来实现某个特定领域的任务。它强调无状态的操作,状态应该在实体中维护,领域服务处理是无状态的逻辑处理过程,实现了某个特定领域的任务,其做的也是领域内的事情,是通用语言的表达。应用服务是领域服务的客户方。

当某个操作不适合放在聚合(实体)或值对象上时,最好的方式便是使用领域服务。

(7)模块(Module):指提供特定功能的相对独立的单元,也就是对功能的分解和组合。我们通常将领域模型分解成不同的模块,来降低领域模型的复杂度,从而提高领域模型的可读性。

(8)聚合(Aggregate):聚合是由聚合根(Root Entity)绑定在一起的对象的集合,是领域对象的显示分组,用来表达整体的概念(也可以是单一的领域对象)。它的作用是保持领域模型的行为和唯一性,同时作为一致性和事务性的边界。聚合根通过禁止外部对象对其成员的引用来保证在聚合内进行的更改是一致的,所以它的难点一般在对一致性的维护上:在聚合内实现强一致性,在聚合外实现最终一致性。在进行聚合设计时要遵循以下几个原则。

◎ 遵循领域不变性。

◎ 每个事务都只包含一个更新聚合,当在事务中有多个更新聚合时,就要通过领域事件对事务进行拆分,实现最终一致性。

◎ 使用小聚合,避免把聚合当作领域对象的集合或容器,从而出现“巨大”的聚合根。

◎ 不仅仅是 HAS-A 的关系,与面向对象(OO)思想中的聚合不是一个概念。领域驱动设计中的聚合首先要确定对象的行为和唯一性,由此可见聚合不是单纯的包含关系。

◎ 领域对象的持久化和检索是通过聚合根来完成的。

◎ 使用值对象,将聚合根内的其他领域对象优先设计成值对象。

◎ 使用ID进行关联,而非对象引用。

◎ 聚合外的对象不能持有聚合内的对象的引用,聚合内的对象可以持有其他聚合内的对象的引用。

◎ 避免在聚合内使用依赖注入。

(9)工厂(Factory):工厂用来封装对象的创建所必需的信息,对创建聚合特别有用。一个对象的创建可能是它自身的主要操作,但是复杂的组装操作不应该成为被创建对象的职责,因为组装这样的职责会产生笨拙的设计,也很难让人理解。而工厂可以帮助封装复杂对象的创建过程,并且当聚合根建立时,所有聚合包含的对象也随之建立了,整个过程是又是原子化的。

(10)仓储(Repository):仓储是对聚合的管理,它介于领域模型和数据模型之间,主要用于聚合的持久化和检索,同时对领域模型和数据模型进行隔离,以便我们关注领域模型而不需要考虑如何进行持久化。肯定有人问:“为什么不能直接使用数据访问层(比如ORM框架等),而需要多加一个仓储层来解耦呢?”下面主要介绍仓储与数据访问层的区别。

◎ 仓储限定了只能通过聚合根来持久化和检索领域对象,以确保所有改动和不变性都由聚合根处理,保证聚合内的一致性。

◎ 仓储通过隐藏聚合持久化和检索的底层技术实现,从而达到与具体实现的解耦能力,即领域层不需要知道通过什么方式来持久化领域对象。比如当前使用的是Hibernate,而未来想改为MyBatis,这样的变动对于领域层来说是完全透明的。

◎ 仓储为数据模型和领域模型定义了一个边界,解决了领域模型和数据模型的混乱情况(很多人都错误地把ER数据模型当作领域模型)。

虽然以上要素的责任不一样,所负责的层级也不同,但是各要素之间并非独立,其关系如图3.30所示。

图3.30

领域模型实践

领域驱动设计中的核心内容是领域模型,本节就来讲解领域模型实践。

什么是领域模型

领域模型(Domain Model)是对现实世界或者领域中对象的可视化表示。领域模型也被分为概念模型、领域对象模型和分析对象模型。领域模型也叫作问题域模型,表述的是某个领域的现实概念。

经常有人把物理数据模型(比如数据库的ER模型)和领域模型混为一谈,其实这是一个误区。物理数据模型在本质上归属于结果域模型(Solution Space Model),是对某个问题域的解决方案的一个具体描述。而领域模型是为了准确定义需要解决的特定问题而构造的抽象模型,其最重要的功能是形成统一的认知,也就是说所有相关人员都需要对要解决的问题有一个完整、规范和一致的认知。

我们经常在公司听到一句话:一颗心,一张图,一场仗,指我们首先要明白解决什么问题(一场仗),才能把这个问题毫无歧义地表述成统一的模型(一张图),有了这模型,我们就能全心全意地投入到解决这个问题的过程中,达到对问题的统一认知(一颗心)。所以,如果大家在工作中经常争论一个问题,则很可能是因为大家对问题的定义还没有统一的认知。而系统、科学、有逻辑地定义我们的问题,从而达到对要解决的问题的统一认知,就是领域建模的过程。

领域建模用于对领域内的概念或现实世界中的对象进行可视化表示,专注于分析问题域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系,所以它是对业务角色和业务实体之间应该如何联系和协作以完成业务功能的一种抽象。

领域模型的作用

领域模型是对业务概念的可视化描述,是需求分析的产物,用于指导程序设计,但领域模型与实现方式无关,在领域建模时不应该考虑如何实现,需要同项目的所有成员(客户、项目经理、开发、测试等)达成共识。

为什么要做领域建模?首先,从20世纪80年代开始,人们对于面向对象建模就已产生许多思考和方法,其中最流行的就是面向对象分析与设计,面向对象分析强调的是在问题域发现并描述概念,解决的问题是做正确的事情;面向对象设计强调的是定义软件对象,解决的问题是正确地做事情。然后,建模的重要性在所有工程实践中都已经得到了广泛的认同,它是一种抽象和分解的方法,可以将复杂的问题拆解成一个个的抽象块,代表特定的一块密集而内聚的信息。最后,领域模型就是面向对象分析的主要产物,表达了对现实问题的描述和抽象,如图3.31所示。

图3.31

如果不做设计,直接实现,则很有可能在开发过程中发现思维的局限性,使开发推倒重来;并且,通过这种方式构造的代码,并没有和现实世界连接起来,当我们的软件和需求稍加修改时,代码就可能变得异常混乱和难以维护。而通过领域建模的自上而下的设计,可以保证代码实现的层次结构和模块划分是科学、稳定的。同时,随着时间的迁移,系统的演变导致系统之间的依赖越来越复杂,因为模型设计与现实世界变得不一致,模块职责变得越来越不明确,系统之间的交互变得越来越复杂,最终整个系统都变得难以维护。

举个例子,在某电商系统的初期设计中,一个卖家账号只能开一个店铺,因此卖家和店铺的概念全部由卖家这一个模型来承载。所有与店铺相关的模型(店铺红包、店铺评价等)全部与卖家模型关联。这时,同一个模型拥有了两层业务含义,职责不明确。

领域建模可以降低软件和现实世界之间的差异,用真实的业务概念划分职责,目的是实现一个可以高效率、低成本维护的可持续发展的软件系统。

从领域模型推导到系统实现是一套引导思考的方式,也是一套科学的开发流程,其核心目的在于提供系统设计的“指导方针”。领域模型必须基于用户需求和业务发展,既可以用来同用户沟通验证需求,又可以避免模型因实现的考量而被带偏(实现成本、遗留系统)。

如何进行领域建模

领域建模的方法也有很多种。下面列出的是一些常见的方法。需要注意,领域建模是需要依赖大量经验和思考的,各种方法只起引导的作用。

1.用例分析法

用例分析法是进行领域建模最简单可行的方式,其步骤如下。

(1)获取用例描述。既然我们的领域模型指的是问题域模型,那么建模也一定要从问题域入手。那么问题域的知识是如何表现出来的呢?一种最常见的方式就是通过用例,也可以通过场景来分析,总之就是一段格式化的需求文字描述。

(2)寻找概念类。寻找概念类就是对获取的用例描述进行语言分析,识别名词和名词短语,将其作为候选的概念类。当然,需求描述中的名词不可能完全等价于概念类,自然语言中的同义词、多义词都需要在此处进行区分。还有很多名词可能只是概念类的属性,不过没关系,在这一步都可以提取出来,在第4步再区分出概念类和属性。

(3)添加关联。关联意味着在两个模型之间存在语义联系,在用例中的表现通常为两个名词被动词连接起来;所有动词关联的概念类并非都需要作为关联存在,更重要的是我们需要判断,两个概念类的关系是否需要被记住;试想你是一个业务员,如果某两个概念类的实例之间的关系没有任何人知道,是否会阻碍业务的开展?如果答案是肯定的,那么说明这两个概念类存在关联;如果答案是否定的,那么建议不要加上关联(视情况而定,也要考虑逻辑上二者的关系是否“被记住”)。应该尽量避免加入大量的关联,关联不代表数据流,也不代表系统调用关系。

(4)添加属性。对于上文抽取的名词列表,我们需要区分概念类和属性(当然,名词列表也会有无用的词语)。如何判断一个名词是否是属性?能完全通过基本数据类型(数字、文本、日期)表达的大多是属性。如果一个名词只关联一个概念类,并且它自身没有属性,那么它就是另一个概念类的属性。注意,这里表达的依然是业务概念,外键 ID 不是属性。

(5)模型精化。这是可选的步骤,有时我们希望在领域模型中表达更多的信息,这时会利用一些新的手段来表达领域模型,包括泛化、组合、子域划分等领域模型都可以使用UML 的泛化和组合来表达模型间的关系,它们表达的是概念类的“IS-A”和“HAS-A”的关系,并不是实现的软件类的关系。然而,在一些方法论中并不建议使用这种方式来表达领域模型,因为这种精化的领域模型不利于和需求方沟通。子域划分是常见的拆解领域的方式,通常来说,我们会将更内聚的一组模型划分为一个子域,形成更高一层的抽象,利于系统地表达和分工。

2.领域驱动设计建模的步骤领域驱动设计本身是一套完整、详尽的方法论,从如何进行需求沟通(构建领域知识),到高层设计(战略建模)、详细设计(战术建模),细致到代码的实现风格都给出了示例。需要再次强调的是,领域驱动设计的模型在本质上是Solution Space的模型,然而领域驱动设计强调模型与实现绑定,因此这里指的“模型”可以说是领域模型,也可以是系统模型。下面就是领域驱动设计建模的一般步骤。

(1)构建领域知识。软件的最终目的是增加一个特定的领域。为了达到这个目的,软件需要与自己要服务的领域“和谐相处”,软件需要精确反映领域的概念和知识,以更好地适应变化。因此,软件开发者第一步且最重要的一步就是理解领域知识。领域驱动设计鼓励开发者和领域专家一起工作,通过交谈和提问,让开发者学习到领域知识,挖掘出领域的关键概念。

(2)创建通用语言。通用语言是领域专家和开发团队之间定义的标准术语,目的是把领域知识更完善地传达到软件中。团队在进行所有方式的沟通时(文字、演讲、图形)都需要采用这种一致的语言。

通用语言需要映射到模型和代码里,对通用语言的更改就是对模型的更改,也是对代码的更改。

(3)创建实体。基于通用的语言和领域知识,我们首先需要分辨出实体。实体具有标识唯一性,两个实体若状态一样但标识不一样,就是两个不同的实体。实体通过属性来描述。

(4)创建值对象。值对象不具有唯一性,如果两个对象的状态一致,它们就是同一个值对象。我们也可以将值对象理解为一组聚合的属性,例如地址信息、类目信息等。

(5)创建聚合根。聚合根是一个实体,将一组模型聚合在一起,与外部模型划分开来。这一组模型全部关联着聚合根,只有聚合根负责与外部的访问。聚合根还有助于保持领域模型关联关系的简化和生命周期的维护。

3.四色建模法四色建模法源于Java Modeling In Color With UML,是一种基于模型的分析与设计方法,通过把所有模型都分为4种类型,使模型清晰、可追溯,如图3.32所示。

图3.32

四色模型如下。

◎ Moment-interval(时标性原型):时标性原型是建模的起点,代表着我们需要记录的某一时刻发生的事件,例如订单、行程和会议。

◎ Party-place-thing(人-事-物原型):是一种有形的可唯一识别的实体,可以是人、机构、地点、物品等。

◎ Role(角色原型):角色是人、事、物的一种参与方式。例如,在一份雇用关系中,某个人扮演者雇员的角色,那么这个人就是人-事-物原型,雇员就是“Role”。

◎ Description(描述原型):表示资料类型的资源,是一种类似目录条目的描述,用来对对象进行分类或标记,可以被其他原型反复使用。例如,一个商品的品牌、描述和属性。

建模次序如下。

(1)在满足运营、管理需求的同时支持事件追溯。

(2)根据需求追溯,找出相应的时标性原型。

(3)寻找时标对象周围的人-事-物。(4)从中抽象角色。

(5)把一些信息用描述对象补足。

4.从领域模型到系统模型

提到模型,大多数人的第一反应就是一个类或者对象,这里指的系统模型就是这种概念。

为了保证程序的实现遵循领域模型的思想,并让所有人都对领域和职责的认知没有偏差,这里强烈建议每个领域模型都要有一个系统模型与之对应,最好能完全一一对应(领域驱动设计就是这么做的),它们的命名和属性也尽可能保持一致,使用相同的术语。具体到系统模型的设计,就是面向对象设计的范畴了,可以使用各种各样的设计模式、GRASP、SOLID 去设计和规划,这里不再赘述。需要遵循的宗旨是,领域模型的模型职责、子域边界划分应该作为此处设计的指导原则,每个模块都不可以突破这些职责的约束。

本文给大家讲解的内容是程序员技术精进: 业务分析与设计,领域驱动设计与模型实践

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关文章
|
3月前
|
存储 机器学习/深度学习 设计模式
笔记 - 《领域驱动设计:软件复杂性应对之道》
笔记 - 《领域驱动设计:软件复杂性应对之道》
|
8月前
|
前端开发 Java 数据库连接
软件开发者的时间都去哪儿了?后端开发核心技能——抽象建模
服务端开发工程师在大部分工作时间里并不是在写代码,而是在抽象建模。工程师需将业务需求抽象成领域模型、模块、服务和系统,面向对象开发时需抽象出类和对象,面向过程开发时抽象出方法和函数。某种意义上,软件的本质就是抽象,建模则是系统地实施抽象的过程。作为一种将事物形象化的有效手段,建模可将现实世界中的事物及事物之间的关系准确地表达出来。本文通过一个实际案例,详细解读业务抽象建模的好处。
软件开发者的时间都去哪儿了?后端开发核心技能——抽象建模
|
12月前
|
架构师 UED
【设计思维框架】框架 :为现代企业重新设想的设计思维(上)
【设计思维框架】框架 :为现代企业重新设想的设计思维
|
12月前
|
安全 数据可视化 测试技术
【设计思维框架】框架 :为现代企业重新设想的设计思维(下)
【设计思维框架】框架 :为现代企业重新设想的设计思维
|
架构师
《架构师修炼之道》第八章--建立模型,化繁为简
项目进入了开发阶段,我们发现团队成员描述同一架构元素时使用的词汇各不相同。我们的设计决策表面上取得了一致意见,但大家实际各有各的理解。
322 0
《架构师修炼之道》第八章--建立模型,化繁为简
|
SQL 前端开发 安全
【测开方法论】如何简单的对测试平台进行底层重构 ?
【测开方法论】如何简单的对测试平台进行底层重构 ?
|
存储 SQL 缓存
技术方案设计的方法论及案例分享
怎么去体现技术方案设计的深度是大家普遍关心的一个问题,这个问题不是个例问题,因此本文主要分享下作者个人的一些观点和看法。
技术方案设计的方法论及案例分享
|
开发框架 自然语言处理 .NET
利用terralang实现terrapp(1):深刻理解其工作原理和方法论
本文关键字:发明自己的语言,可lua扩展的语言系统,用库发明语言vs用语言发明语言,是toolchain语言也是app生产语言,全生态语言
299 0
利用terralang实现terrapp(1):深刻理解其工作原理和方法论