CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
这是篇关于DDD的文章,我也不知道叫啥好(一)
0
架构设计
小笨蛋
发布于:2022年05月21日
更新于:2022年05月31日
111
#custom-toc-container
> 关于DDD有太多想说的,上家公司让我从DDD的理论到DDD的实践踏实的走了一圈,也让我开始反思做为一个设计者如何在项目中真正的实践DDD,而又不出现过度设计的尴尬,要找到一个平衡点的前提是要对DDD有比较深入的了解。这个系列我准备用两篇,额,整不好得用三篇才能说的明白DDD。 > 推荐阅读《实现领域驱动设计》,这些内容都来自于这本书 ### 1.通用语言 #### 1.引言 在开始之前,我想我们有必要先了解以下DDD的主要参与者。因为毕竟语言是人说的吗,就像我们面向对象编程一样,那通用语言面向的是? DDD的主要参与者:领域专家+开发人员 1. 领域专家:精通业务的任何人。(大概率是公司的产品) 3. 开发人员:开发+测试。 领域专家擅长某个领域的知识,专注于交付的业务价值。 开发人员则注重于技术实现。 开发人员总是想着类、接口、方法、设计模式、架构等。以面向对象的编程思想进行思考,思考如何进行抽象、封装、继承、多态等。而领域专家对软件中的框架、持久化、数据库等没有概念,而这也就导致了他们之间交流的困难性。 那怎么解决交流障碍这个问题呢? #### 2. 通用语言 那DDD中通用语言是怎样呢? 首先它要拥有【简单】的特性,这样才便于理解和传播。 其次,它也要有【通用】、【使用率高】的特性,因为只有在软件开发的过程中,团队范围内所有的参与人员广泛使用,才能准确传递业务规则。 通用语言是团队交流的基础上建立起来的,代码则是基于通用语言来进行业务表达的。 #### 3. 举个例子 项目经理安排了一项任务给我: 大佬,这个迭代处理下【可销控制】这个需求。我听后,真是一脸懵逼,【可销控制】是什么鬼? 这明显是专业术语,我不懂的专业术语。按照DDD对通用语言的定义,【可销控制】就不算通用语言。因为只有领域专家知道它的含义,开发人员一脸茫然。但通用语言是领域专家和开发人员一起创建的,所以我们开个需求会议,来梳理下专业术语背后的含义。 可销控制的应用场景是: 在ERP中,在做单时对销售员负责的客户进行范围控制。比如,A客户是销售员小李发展过来的,小李希望仅能自己负责对A客户的业务。 这么一解释,是不是明白不少了。但是不是还有几点疑惑? 1. 可销范围如何配置? 2. 做什么单据时进行可销范围控制? 3. 如何进行可销控制? 4. 可销控制需不需要参数控制? 经过讨论,得出以下结论: 提供专门的【销售员-客户可销控制】界面进行销售员-客户映射数据配置。做销售订单单据时进行可销范围控制在录入销售员后,做单员在选择客户列表时,客户列表中仅显示可销范围配置的客户数据。提供【销售员-客户可销范围控制】系统参数,勾选后才进行可销范围控制。 好了,这下需求理解个七八九了。原来可销控制就是销售员-客户销售范围控制。咱们用一句话来精简下这个需求: > 可销控制是指销售员所负责客户的范围控制,当勾选了【销售员-客户可销控制】参数后,在做销售订单录入客户数据时,客户列表仅能选择到在【可销控制】列表中为该销售员配置的客户。 经过这么一说明,我们是不是已经理清了【可销控制】这个需求点。那这种**通过团队交流达成共识的能够简单清晰准确传递业务规则的语言(可以是文字、图片等)**即可称为通用语言。 #### 4. 通用语言的价值 就像上面所说的那样,通用语言的最大价值是解决了交流障碍问题,使领域专家和开发人员能够协同合作,从而能够确保业务需求的正确表达。 另外,基于通用语言,开发人员能够开发出可读性更好的代码,从而将业务需求准确转化为代码设计。达到DDD的目标代码即设计,设计即代码。通俗的讲,也就是开发人员写的代码领域专家也能看懂。 #### 5.通用语言的代码表达 既然开发人员也要基于通用语言进行代码开发,那代码如何体现通用语言呢? 在《实现领域驱动设计》书中有一个简单的例子(P238),我们一起来看一下: - 系统必须对User进行认证,并且只有当Tenant(租户)处于激活状态时才能对User进行认证。 上面这个用例就是基于通用语言的用例,简单清楚的说明了业务规则。 我们先看第一种代码实现: ```csharp bool anthentic = false; User user = _userRepository().FindUserByTenantIdAndUserName(tenantId, userName); if(user!=null) { authentic = user.IsAuthentic(password); } return authentic; ``` 这段代码完全不能反应通用语言,主要存在以下问题: 1. 这段代码先查找user,再对user进行密码匹配来完成认证。其中`user.IsAuthentic(password);`表示的是“用户是否被认证”的意思,而没有表达出“认证”这个过程,即“对用户进行认证”。 2. 未体现“检查Tenant是否处于激活状态”这个前提条件。 知道问题后,我们可以将代码略做改动: ```csharp bool anthentic = false; Tenant tenant = _tenantRepository.FindTenantById(tenantId); //检查租户是否激活 if (tenant != null && tenant.IsActive) { User user = _userRepository.FindUserByTenantIdAndUserName(tenantId, userName); if (user != null) { authentic = tenant.Authenticate(user, password);//租户对用户进行认证 } } return authentic; ``` 以上代码虽然也不是最终结果,但至少对通用语言进行了体现。通读代码,就能明白业务用例,体现代码即设计这一思想。 ------------ ### 2.领域 #### 1. 引言 领域一词,主要有以下两个意思: 1. 一国主权所达之地。 2. 学术思想或社会活动的范围。 不管是指国家的主权范围也好还是学术活动范围,都是在讲一个范围,一个界限。 比如我们常说的,学术领域、思想领域、技术领域、语言领域、物理领域、医学领域、游戏领域、JAVA领域、.NET领域等等,它们中不管是泛指还是特指某个领域,都是限定在某个范围之内的。由此可见领域一词重在范围的界限。 下面我们就回归正传,DDD,`Domain Drive Design`,全称:领域驱动设计。那这个领域具体指什么呢,在DDD中有什么特殊含义呢? #### 2. DDD中的领域 我们要先弄明白DDD是干什么的。 DDD是一种综合软件系统分析和设计的面向对象建模方法,旨在帮我们设计高质量的软件模型,是一种解决复杂中大型软件的一套行之有效的方式。 那既然是用来解决软件问题,那自然要弄清楚软件系统的业务需求。那就以我之前的办公设备微信公众号开发的案例为例,来分析一下。 虽然是微信公众号开发,但其实涉及到的主要是商品、用户、订单、报价、支付、物流、报修相关业务,其本质还是电子商务系统开发。反过来看,任何电子商务网站也都涉及到商品、用户、订单、支付、物流这些核心业务,而这也正是所有电商领域都会涉及到的业务。 其实不管是普通的电子商务网站,还是像京东、淘宝、亚马逊这样的电商平台,其本质都是电商领域,只不过是系统的复杂性不同而已。所以我们的案例也就可以理解为一个基于微信公众号平台的电商领域普通电子商务网站的开发。 通过以上的举例说明,只是想说明,DDD中的领域也并没有什么特别之处,它只是被界限在指定的业务需求之中,有了更清楚的范围边界。其实也可以参照原著《领域驱动设计:软件核心复杂性应对之道》的总结: > 一个领域本质上可以理解为就是一个问题域,只要是同一个领域,那问题域就相同。所以,只要我们确定了系统所属的领域,那这个系统的核心业务,即要解决的关键问题、问题的范围边界就基本确定了。 #### 3. 子域 回到我们的案例,基于微信公众号的电子商务网站的开发。 我们先抛开DDD,按照我们传统的开发设计思路,以数据优先的方式,上来就建表写代码,而且按照我们一贯的作风,肯定会先设计Product表,因为电商的哪一个环节都跟商品息息相关,在这里Product对象就是一个核心对象。那既然Product对象是核心对象,那Product对象所处的领域--商品列表领域,是不是就是我们这一节讲的核心域?**答案是否定的。** DDD跟我们传统的软件开发设计过程不同,它**注重领域建模**。 在了解了业务领域的业务规则之后,我们为了完成对一个复杂领域的分析,往往我们要先简化之,即领域拆分。可以理解为把领域中的复杂大问题,拆分成小问题,各个击破。也就是将一个领域拆分成多个子域,再针对每个子域进行分析。而子域又可以分为核心域、通用子域、支撑子域。 那什么是核心域、通用子域、支撑子域呢? 下面我们就对我们的案例进行拆分来梳理这些概念。 对于我们的案例涉及到的业务,商品、用户、订单、报价、支付、物流、报修相关业务,我们可以大致拆分成以下几个子域: - 商品子域 - 用户子域 - 销售子域 - 订单子域 - 支付子域 - 物流子域 - 维修子域 ##### 3.1 核心域 如果从几个子域中确定核心域,这也许难以决定,没关系。我们先来回答一个问题。 什么是电商网站?在线**商品买卖**平台。 电商领域的核心就是为了**商品买卖**,我们知道开发这个电商网站的目的是为了寻求推广和销售利润的最大化。所以自然而然核心域就是销售子域了。 为什么要先确定核心域? 因为作为一个业务的核心存在,它最能体现系统的核心价值,也是核心竞争力。如果要最大化系统的价值,我们必然要在核心域的设计上更胜一筹。确定核心域后,我们在进行开发设计的时候就有了主次之分。 ##### 3.2 通用子域 通用子域,顾名思义,也就是服务于整个业务领域。比如我们要为该网站提供一个日志系统,用来记录一些日志。我们可以设计一个日志子域来供其他子域使用。 ##### 3.3 支撑子域 支撑子域的作用于业务系统的某些重要业务而非核心业务,它关注于业务的某一方面,来支撑完善业务系统。我们划分的子域中除了销售子域,其他都可以说是支撑子域。比如物流子域,就是专注于物流相关业务,支撑着订单发货以及物流跟踪的重要流程。 #### 4. 总结 本节主要结合案例,简要梳理了DDD中领域、核心域、通用子域、支撑子域的定义。 - 领域是有范围界限的,也可以说是有边界的。 - 核心域是业务系统的核心价值所在,承载着一个系统的重中之重。 - 通用子域可以理解为业务系统所有子域的消费者,提供着通用服务。 - 支撑子域专注于业务系统的某一重要的业务,来支撑和完善业务系统。 这一节遗留了一个问题,那就是文章开头就说领域是有范围界限的,但这个范围界限在DDD中如何表述呢?这就是下一节要说的——限界上下文。 ------------ ### 3.限界上下文 #### 1. 引言 限界上下文可以拆分为两个词,限界和上下文。 限界:是指一个界限,具体的某一个范围。 上下文:个人理解就是语境。 比如我们常说的段子: > “我想静静。” 这个句子一般是想表达“我想静一静”的意思。但是我们却把它玩笑成“静静是谁?”。 可见上下文语境很重要。 #### 2. 案例分析 > 整个应用程序之内的一个概念性边界。 边界之内的每种领域术语、词组或句子--也即通用语言,都有确定的上下文含义。 边界之外,这些术语可能表示不同的意思。 每次看到这种解释就头大。我们还是结合我们的案例来聊一聊吧。 根据上一节对领域的剖析,我们把案例主要拆分成几个子域,其中销售子域是核心域,商品子域和物流子域为支撑子域。在这三个子域中,都要和商品打交道。如果把商品抽象为Product对象的话,按我们一般的常规思路(抛开子域的划分)来说,不管是商品销售还是发货,我们都可以共用同一个Product对象。 但在DDD中,在商品子域和销售子域中,可以共享这个Product对象,但在物流子域,就有点大材小用。为什么呢?因为毕竟物流子域关注的是商品的发货处理和物流跟踪。针对发货流程而言,我只关心商品的数量、大小、重量等,而不必了解商品的价格等其他信息。所以说物流子域应该关注的是货物的发货处理而不是商品。 那为什么我们之前的开发思路会共用同一个Product对象呢? 答案很简单,没有进行领域的划分。把整个项目一概而论,这是统一建模导致的结果。 在DDD的思想下,当划分子域之后,每个子域都对应有各自的上下文。在销售子域和商品子域所在的上下文语境中,商品就是商品,无二义性。在物流子域的上下文语境中,我们也可以说商品的发货处理,但这时的商品就特指货物了。确定了真实面目之后,我想我们也会不由自主的抽象一个新的Cargo对象来处理物流相关的业务。这也是DDD带来的好处,让我们更清晰的建模。 #### 3. 限界上下文的命名 限界上下文只是一个统一的命名,在我们划分子域后,每个子域一般对应一个上下文,也可以对应多个上下文。但如果子域对应多个上下文的时候,就要考虑一下是不是子域能否继续划分。 命名方式很简单,领域名+上下文。 比如我们的销售子域对应销售上下文,物流子域对应物流上下文。 #### 4. 总结 通过我们上面的举例分析,限界上下文也并不是一个高深的概念。 用官话来说限界上下文主要用来封装通用语言和领域对象。 按我个人的理解它就是用来为领域提供上下文语境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。 ------------ ### 4.领域模型 #### 1.引言 我们还是先来拆词理解,领域模型可以拆为“领域”和“模型”二词。 - 领域:按照我们之前的文章的理解,DDD中的领域是指软件系统要解决的问题,如我们的办公设备公众号在线商城就是为了解决电商问题,对应的就是电商领域。 - 模型:百度百科解释为对于某个实际问题或客观事物、规律进行抽象后的一种形式化表达方式。如户型图就是实际房屋结构的模型。 把两个词结合起来,我们给领域模型下个定义:领域模型是对我们软件系统中要解决问题的抽象表达。 这个理解还是很生涩,没关系,容我娓娓道来。 #### 2.领域模型的来历和作用 我们知道,软件开发过程主要包括:需求分析、概要设计、详细设计、编码、测试、软件交付、验收、维护。其实简单来说就是分析、设计和实现。 而传统的软件开发方式中,系统分析、设计和实现三个阶段完全脱节,最后开发出来的软件不能很好的满足业务需求,在未来也不能很好的适应需求变化进行功能演进。 那在DDD中是如何做到呢,下面我们就从以下几个问题来分析说明。 1. 怎样确保最终的软件设计能满足客户需求且适应变化? 那就要保证系统分析、设计和实现不脱节。 2. 那如何做到不脱节呢? 如果按照我的理解,那就需要有某一个东西能贯穿整个开发流程,来衔接分析、设计和实现三个阶段。 3. 那这个东西是什么呢? 聪明如你,是的,就是我们今天的主题——领域模型。 4. 那领域模型是如何做到的呢? 在分析阶段,所有的参与人员(领域专家、设计人员、开发人员等)对业务进行需求分析,通过大家的不断交流讨论,提取出业务规则和流程中的关键词汇和概念形成通用语言,进而发现领域概念,随着大家对领域的认识不断深入,通用语言的词汇也会不断丰富和精准,从而确保了业务需求的正确表达。 在设计阶段,以通用语言为交流基础,将发掘的领域概念进行领域模型设计,以面向对象的思想抽象出实体,确定实体所对应的方法和属性,以及实体之间的关系。然后将这些实体和实体之间的关系以某种形式展现出来,形成领域模型。 在实现阶段,开发人员根据确立的领域模型进行代码实现,做到代码与模型的绑定,从而实现了设计和实现阶段的衔接。 通过这样一种方式,我们实现了语言、模型、代码三者紧密绑定,确保开发出来的软件来准确反应需求并能适应变化。 通过上面对领域模型的来历和作用的介绍,我们对领域模型就有了一个大致的印象。 #### 3.案例分析 按照上面的理解,领域模型无非就是综合了系统分析和设计的产物,而这个产物我们正好可以通过UML来展示,下面我们就结合办公设备微信公众号在线商城案例,简单对销售子域进行领域模型设计。 ![图片alt](/uploads/images/20220521/145621-5b222ce2bea94c4486e6da89079c90fd.png '代码片段:Www.CodeSnippet.Cn') 从该销售子域的UML类图中,我们可以看出它包含了销售子域涉及到相关实体以及实体之间的关系。只要看到这个类图,我们就知道它涉及的相关概念和流程。所以说上面这张UML类图是销售子域的领域模型也不为过。 #### 4.总结 领域反应的是我们业务上需要解决的问题,模型是我们针对该问题提出的解决方案。综合来说,领域模型就是用来描述我们正在解决的问题和提出的解决方案。 领域模型按照我个人的理解,就是将业务中涉及到的概念以面向对象的思想进行抽象,抽象出实体对象,确定实体所对应的方法和属性,以及实体之间的关系。然后将这些实体和实体之间的关系以某种形式(比如UML、图形、代码、文字描述等)展现出来。 以上只是领域模型理论上的理解,但领域模型的设计(领域建模)却是另一个复杂的话题。 ------------ ### 5.统一建模语言 #### 1.引言 上一节讲解了领域模型,而领域模型是领域建模的结果,那如何建模呢?我们可以借助于UML。 我们知道UML(统一建模语言)是一种用于绘制软件概念图的图形符号。在和他人交流以及帮助解决设计问题方法,图示是最有效的。 插一嘴,UML这玩意我画的少,主要是机会少,所以里面好多关系图示都记不住(PS:考信息系统项目管理师的时候还特意背过,但后来就忘了!),每次都要网上搜索,正好这篇文章把UML相关知识也放里。 #### 2.中途意外出现的UML - 虚线箭头指向依赖; - 实线箭头指向关联; - 虚线三角指向接口; - 实线三角指向父类; - 空心菱形能分离而独立存在,是聚合; - 实心菱形精密关联不可分,是组合; [![](/uploads/images/20220521/141830-42e4a91a4f6b4fdd991df4669d9f5527.jpg)](https://www.codesnippet.cn) #### 3. UML的级别和类别 UML主要被分为三个级别: - 概念级别:用来描述问题领域中概念和抽象的一种速记方法,没有比较严格的语义规则。和源代码之间没有很强的关联性。 - 规格说明级别:描绘问题的解决方案,目的是为了能够转换成源代码。要遵循严格的语义规则。 - 实现级别:用来描绘已有的源代码,如类图。要遵循严格的语义规则。 UML主要有三种图示类别: - 静态图(static diagram):描述了类、对象、数据结构以及它们之间的关系,展现出软件元素间不变的逻辑结构。类图、对象图都是静态图。 - 动态图(dynamci diagram):展示软件实体在运行过程中是如何转换的,其中描述了运行流程或实体改变状态的方式。顺序图、协作图、状态图都是状态图。 - 物理图(physical diagram):展示软件实体不变的物理结构,描述了诸如源文件、库、二进制文件、数据文件等物理实体以及它们之间的关系。 #### 4.总结 UML本身是一个复杂的东西,要完全掌握它是需要耗费很大时间和精力。但是我们在建模时要本着越少越好的思想去使用它。不要过于追求图示的详细程度,且UML图不是源代码,没有必要申明所有方法、变量和关系。在学习UML的时候,不建议一上来就去找一些UML画图工具,直接在纸上写写画画就好。 最后,最最最重要的是,请动手画! ------------ ### 6.实体 #### 1.引言 实体对应的英语单词为Entity。提到实体,你可能立马就想到了代码中定义的实体类。在使用一些ORM框架时,比如Entity Framework,实体作为直接反映数据库表结构的对象,就更尤为重要。特别是当我们使用EF Code First时,我们首先要做的就是实体类的设计。在DDD中,实体作为领域建模的工具之一,也是十分重要的概念。 但DDD中的实体和我们以往开发中定义的实体是同一个概念吗? 不完全是。在以往未实施DDD的项目中,我们习惯于将关注点放在数据上,而非领域上。这也就说明了为什么我们在软件开发过程中会首先做数据库的设计,进而根据数据库表结构设计相应的实体对象,**这样的实体对象是数据模型转换的结果。** 在DDD中,实体作为一个领域概念,在设计实体时,我们将**从领域出发**。 #### 2.DDD中的实体 DDD中要求实体是唯一的且可持续变化的。意思是说在实体的生命周期内,无论其如何变化,其仍旧是同一个实体。唯一性由唯一的身份标识来决定的。可变性也正反映了实体本身的状态和行为。 #### 3. 唯一标识 举个例子,在有双胞胎的家庭里,家人都可以快速分辨开来。这得益于家人对双胞胎性格和外貌的区分。然而邻居却不能,只能通过名字来区分。上小学后,学校里尽然有重名的,这时候就要取外号区分了。上大学后,要坐火车去学校,买票时就要用身份证号来区分了。 针对这个例子,如果我们要抽象出一个User实体,要如何定义其唯一标识呢? 其中性格、外貌、昵称、身份证号都可以作为User实体的属性,在某些场景下某个属性就可以对对象进行区分。但为了**确保标识的稳定性**,我们只能将身份证号设为唯一身份标识。 ##### 3.1.唯一标识的类型 唯一标识的类型在不同的场景又有不同的要求。 主要可以分为有意义和无意义两种。 在一个简单的应用程序里,一个int类型的自增Id就可以作为唯一标识。优点就是占用空间小,查询速度快。 而在一些业务当中,要求唯一标识有意义,通过唯一标识就能识别出一些基本信息,比如支付宝的交易号,其中就包含了日期和用户ID。这种就属于字符串类型的标识,这就对唯一标识的生成提出了挑战。 在一些复杂的业务流程中,对唯一标识没有要求,我们可以使用GUID类型来生成唯一标识,很显然GUID占用空间就毕竟大,且不利于查询。(就是为了唯一而存在的) ##### 3.2.唯一标识的生成时机 有某些场景下,唯一标识的生成时机也各不相同,主要分为即时生成和延迟生成。 - 即时生成,即在持久化实体之前,先申请唯一标识,再更新到数据库。 - 延迟生成,即在持久化实体之后。 ##### 3.3.委派标识和领域标识 基于领域实体概念分析确定的唯一身份标识,我们可以称为领域实体标识。 而在有些ORM工具,比如Hibernate、EF,它们有自己的方式来处理对象的身份标识。它们倾向于使用数据库提供的机制,比如使用一个数值序列来生成识。在ORM中,委派标识表现为int或long类型的实体属性,来作为数据库的主键。很显然,委派标识是为了迎合ORM而创建的,且委派标识和领域实体标识无任何关系。 那既然ORM需要委派标识,我们就可以创建一个实体基类来统一指定委派标识。而这个实体基类又被称为层超类型。 ###### 3.3.1.实现层超类型 首先定义层超类型接口: ```csharp public interface IEntity { } public interface IEntity
: IEntity { TPrimaryKey Id { get; set; } } ``` 通过定义泛型接口,以支持自定义主键类型。 实现层超类型: ```csharp public class Entity : Entity
, IEntity { //啦啦啦 } public class Entity
: IEntity
{ public virtual TPrimaryKey Id { get; set; } public override bool Equals(object obj) { if (obj == null || !(obj is Entity
)) { return false; } if (ReferenceEquals(this, obj)) { return true; } //必须具有类型的IS-a关系或必须是相同的类型 var typeOfThis = GetType(); var typeOfOther = other.GetType(); if (!typeOfThis.GetTypeInfo().IsAssignableFrom(typeOfOther) && !typeOfOther.GetTypeInfo().IsAssignableFrom(typeOfThis)) { return false; } return Id.Equals(other.Id); } public override int GetHashCode() { return Id.GetHashCode(); } public static bool operator ==(Entity
left, Entity
right) { if (Equals(left, null)) { return Equals(right, null); } return left.Equals(right); } public static bool operator !=(Entity
left, Entity
right) { return !(left == right); } } ``` 可以看到默认的委托标识为int类型。我们重写了Equals,GetHashCode方法,以及==和!=两个操作符。 通过这样一种方式,我们进行约定,所有的实体必须继承自`Entity`,即可实现委托标识的统一定义。 #### 4.可变性 解决了实体的唯一身份标识问题后,我们就可以保证其生命周期中的连续性,不管其如何变化。 那可变性说的是什么呢?**可变性是实体的状态和行为**。而实体的状态和行为就要对具体的业务模型加以分析,提炼出通用语言,再基于通用语言来抽象成实体对应的属性或方法。 > 我们拿订单环节来举例说明: 当顾客从购物车点击结算时创建订单,初始状态为未支付状态,支付成功后切换到正常状态,此时可对订单做发货处理并置为已发货状态。当顾客签收后,将订单关闭。 从以上的通用语言的描述中(在通用语言的术语中,名词用于给概念命名,形容词用于描述这些概念,而动词则表示可以完成的操作。) 我们可以提取订单的相关状态和行为: - 订单状态:未支付、正常、已发货、关闭。针对状态,我们需定义一个状态属性即可。 - 订单的行为:支付、发货和关闭。针对行为,我们可以在实体中定义方法或创建单独的领域服务来处理。 实体既然存在状态和行为,就必然会与事件有所牵连。比如订单支付成功后,需要知会商家发货。这时我们就要追踪订单状态的变化,而追踪变化最实用的方法就是领域事件。关于领域事件,我们后续再讲。 #### 5.实体的验证 验证的目的是为了检查模型的正确性和有效性。检查的对象可以为某个属性,也可以是整个对象,或是多个对象的组合。针对验证的方式,不一而足,根据需要可自行发挥。 #### 6.总结 实体作为领域建模的工具之一,唯一的身份标识是实体最基本的特征,其次是可变性。唯一身份标识和可变性也是用来区分实体和值对象的主要特征。 为了正确建立实体模型,我们需要将关注点从数据转向领域,从业务模型中提炼通用语言,再基于通用语言分析其状态和行为。 所以,我们可以认为:**实体 = 唯一身份标识 + 可变性【状态(属性) + 行为(方法或领域事件或领域服务)】 **
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024