从公众号转载,关注微信公众号掌握更多技术动态
---------------------------------------------------------------
一、【DDD】领域驱动设计简介
1.什么是DDD——应对复杂性的利器
DDD不是架构,而是一种架构设计方法论,它通过划分领域边界将复杂业务领域简单化,从而设计出清晰的领域和应用边界,可以很容易实现架构演进。可以利用DDD设计方法来建立领域模型,划分领域边界,再根据这些领域边界从业务视角划分微服务边界。
从空间上来讲,它看到的是整个行业或者整个领域。从时间上来讲,它看到的是软件从发生、发展到消亡的整个生命周期。从角色上来讲,它看到的是业务、产品、开发和运维等所有参与人员的合作。
2.微服务与DDD的关系
DDD是一套综合软件系统分析和设计的面向对象建模方法,微服务是业务模型的系统落地方案。
DDD主要关注:从业务领域视角划分领域边界,构建通用语言进行高效沟通,通过业务抽象,建立领域模型,维持业务和代码的逻辑一致性。
微服务主要关注:运行时的进程间通信、容错和故障隔离,实现去中心化数据管理和去中心化服务治理,关注微服务的独立开发、测试、构建和部署。
3.使用DDD的原因
传统mvc模式不符合java面向对象的思想,实体只是一个用于承载数据的载体,毫无业务逻辑,可以说mvc是一种面向过程的模式。由于在实现一个服务的时候是通过过程一步一步完成的,业务逻辑、各种状态散布在大量的函数中,就会产生很多重复代码,增加了开发的时间,同时代码维护的难度也增加了。所以在业务逻辑复杂的微服务系统之中推荐使用DDD(简单增删改查的系统不必使用)。
如果你投入了大量的人力物力和时间,却没有收到相应的成果,就有点得不偿失了。这种情况下就不推荐使用领域驱动设计了。具体来说,适合使用领域驱动设计的系统需要具备这些特点:
-
系统组件足够多。
-
业务逻辑足够复杂。
-
软件生命周期长。
4.DDD的优点与缺点
(1)DDD带来的好处
-
业务逻辑内聚到业务领域层,可以更好的保证业务规则的一致性;
-
软件的可维护性和可扩展性增强,工程师可以聚焦在业务领域层,并致力于业务领域模型的迭代和维护,适应新业务的发展;
-
软件的可测试性增强,领域层代码不需要借助用户接口层的入口便可以独立测试业务逻辑.
(2)DDD缺点
-
对工程师有较高的业务建模技能要求,期望他们能从复杂的业务上下文中识别出正确的业务模型,并将各个业务行为归入合适的entity/value object/domain service中;
-
项目前期需要投入更多的时间进行业务建模,不能快速进入开发阶段;
-
需要资深的业务领域专家参与进来,否则业务模型的频繁重构(重大改动)会带来额外成本;
5.DDD 使用的误区
(1)所有的领域都用 DDD
DDD 从战略设计到战术设计,是一个相对复杂的过程,首先企业内要培养 DDD 的文化,其次对团队成员的设计和技术能力要求相对比较高。在资源有限的情况下,应聚焦核心域,而不必一下就在全业务域推开。
(2)全部采用 DDD 战术设计方法
不同的设计方法有它的适用环境,应选择它最擅长的场景。DDD 有很多的概念和战术设计方法,比如聚合根和值对象等。聚合根利用仓储管理聚合内实体数据之间的一致性,这种方法对于管理新建和修改数据非常有效,比如在修改订单数据时,它可以保证订单总金额与所有商品明细金额的一致,但它并不擅长较大数据量的查询处理,甚至有延迟加载进而影响效率的问题。
而传统的设计方法,可能一条简单的 SQL 语句就可以很快地解决问题。而很多贫领域模型的业务,比如数据统计和分析,DDD 很多方法可能都用不上,或用得并不顺手,而传统的方法很容易就解决了。因此,在遵守领域边界和微服务分层等大原则下,在进行战术层面设计时,应该选择最适合的方法,不只是 DDD 设计方法,当然还应该包括传统的设计方法。这里要以快速、高效解决实际问题为最佳,不要为做 DDD 而做 DDD。
(3)重战术设计而轻战略设计
DDD 是一种从领域建模到微服务落地的全方位的解决方案。战略设计时构建的领域模型,是微服务设计和开发的输入,它确定了微服务的边界、聚合、代码对象以及服务等关键领域对象。领域模型边界划分得清不清晰,领域对象定义得明不明确,会决定微服务的设计和开发质量。没有领域模型的输入,基于 DDD 的微服务的设计和开发将无从谈起。因此不仅要重视战术设计,更要重视战略设计。
战略设计部分指导如何拆分一个复杂的系统,战术部分指导我们对于拆分出来的单个子系统如何进行落地,在落地过程中应该遵循哪些原则。
6.人员组织架构
软件开发过程中涉及到三类人,第一类是业务方;第二类就是系统分析人员,包括产品经理和架构师;还有一类是系统开发人员。相应地,软件开发流程有 4 个步骤:
-
业务方提出需求。
-
产品经理分析需求。
-
架构师根据产品经理的分析做出合理的架构设计。
-
开发人员按照产品文档和架构设计文档来进行开发。
这个流程看上去很好,但是在实际操作中会有一些问题。比如当开发人员发现文档细节不够全面,甚至内容有误怎么办呢?这时候,沟通只能原路返回,等产品或者业务方再次确定清楚后,再从上往下传递消息。 尝试从宏观角度分析这个沟通问题。软件开发过程中不同角色之间的沟通,其实是一个内容翻译的过程。由于每一步沟通都会造成信息损失,沟通的流程越长,信息损失越大。那怎么解决沟通中的信息丢失问题呢?领域驱动模型提了一个根本的解决方案。既然沟通的流程长了会丢失信息,那么最优化的沟通方式不就是只有一个层级的沟通吗?所以领域驱动设计取消了常见的垂直沟通方式,变成小组沟通方式。每次沟通都是所有相关人员参与。
表面上这只是沟通方式上的一点小小的改变,但其实是一个文化的转变,时间长了会带来一些根本性的变化。
-
第一个变化是提高了决策的速度。在小组沟通的模式下,问题能直接从发现的人手上交给解决问题的人,不再需要层层分析,层层审批。
-
第二个变化是打破了部门壁垒。我们在开篇提到了领域驱动设计需要有一定的业务复杂度。复杂业务会有自己独有的专业术语。行业充满了生僻的术语,开发人员想要正确地进行系统建模,前提就是先对这些术语有正确的了解。那怎样才能了解呢?答案很简单,就是直接参与。开发人员需要直接和业务方沟通,甚至直接参与业务。同样,业务方也很难理解开发人员的术语,也需要通过直接沟通来了解一些基本的行业逻辑和假设这个沟通过程中,会沉淀下来对专业术语的共同理解,这就是领域语言(DomainLanguage)。有了领域语言之后,各方的沟通再也不是鸡同鸭讲,而是永远在一个频道上。
-
第三个变化是弱化了产品经理的角色。产品经理负责收集业务问题,分析后将需求交给开发人员。那问题来了。谁对业务最了解呢?在金融行业,开发人员才是最了解金融业务的人。这一点可能会超出你的想象,但是事实如此。金融行业讲究细节,每一个犄角旮旯的情况都要考虑清楚。开发人员在写程序的过程中,需要对金融业务的正常和异常情况进行极为完整的分析,所以不得不深入了解金融业务。
领域驱动设计建议软件的所有参与方之间能以小组的形式进行直接沟通。开发要懂业务,业务方和产品也要懂技术。沟通的结果是形成一个大家都能认同和
理解的领域语言。
二、核心概念讲解
1.领域简介(Domain)
软件系统的目的都是来解决一系列问题,例如做一个电商系统来在线销售自己企业的产品。任何一个系统都会属于某个特定的领域,例如:
-
论坛是一个领域:要做一个论坛,那这个论坛的核心业务是确定的:比如用户发帖、回帖等核心基本功能;
-
电商系统是一个领域:只要是电商领域的系统,那核心业务就是:商品浏览、购物车、下单、减库存、付款交易等核心环节;
同一个领域的系统都具有相同的核心业务,因为他们要解决的问题的本质是类似的。因此可以推断:一个领域本质上可以理解为一个 问题域 。只要确定了系统所属的领域,那么这个系统的核心业务,即要解决的关键问题就基本确定了。
(1)核心领域 组件之间的重要性还是有微妙的区别。比如说,同样是对外发消息的网关组件,券商对接交易所的网关明显比短信通知网关更重要。那你的这种“感觉”究竟是从哪里来的呢?一个软件组件是否重要,这取决于它所属的业务是不是核心业务。而业务是不是核心又取决于竞争对手。从整个金融行业的角度来看,一家金融公司如果能存活下来,一定需要有比其它类似金融公司做得更好的地方,也就是它的竞争优势。 在 08 年经济危机的时候,有的金融公司计算风险的速度快,就能提前逃离市场。如果你计算得慢,就得被迫吞下有毒资产。这时候的金融行业的竞争优势就是风险的计算和对冲,风险计算就是核心业务领域,风险计算组件就是核心组件。 所以可以这样理解,一个业务是否是核心业务,取决于它是否能给公司带来行业内的竞争优势。核心领域的三个要点,分别是资源分配、审时度势以及宏观视角。
-
资源分配:核心竞争力是买不来的。所以核心领域所对应的软件组件一般建议自研。同时建议把公司最好的人手放在核心领域的开发项目组中。另外,由于核心竞争力需要不断升级,还要做好长期维护的准备。领域驱动设计的一个侧重点是投资回报比。核心领域是重要的赢利点,所以它需要有足够的时间和资源投资来保证未来的竞争优势。
-
审时度势:一家公司的核心竞争力是瞒不住的。稍微过段时间就会有人学会你的赚钱之道,这时候你们之间的竞争就变成了公平竞争,你就再也没有核心竞争力了。 08 年金融危机的时候,有的金融公司靠着超前的风险计算能力躲过了危机。很快怎么复制这种计算方法不再神秘,这时核心竞争力就变成了谁能最快地上线风控模型,以及谁的计算成本最低。所以想判断核心领域,就需要合理判断此时此刻公司所处的行业位置,在可预见的未来对手会如何应对,以及自己应该如何针对性地升级业务和系统。
-
宏观视角:每家公司的核心竞争力都不相同,所以一家公司的核心领域可能对其它公司来说并不是核心。比如,尽管同属于金融行业,券商的会计系统不是最核心的领域,但是对于一家提供会计服务的公司来说,会计系统是它最核心的领域。所以核心组件不能根据其内容来一刀切,而是要根据它属于哪一个更大的环境,再来做灵活判断。那怎么宏观地判断一个核心领域呢?这就要看它能不能给你带来超额利润。超额利润是一个经济学的名词,表示超出一般行业水平的利润。比如对券商来说,能不能发短信通知给用户其实对盈利水平的影响不大。但是如果能提高用户下单的速度,那就完全不一样了。
(2)通用领域 顾名思义,通用领域是可以在不同行业通用的领域。比如我们前面提到的短信平台,不只是金融公司可以使用,物流公司也可以使用。类似的还有安全、日志、存储等等。既然是通用领域,那么市场上一定会有多个类似的产品,而且也会存在第三方服务提供商。领域驱动设计强调投资回报比,所以当市场上存在多个类似的产品的话,我们要尽量采购相关服务或者产品,而不是自己研发。 同一件事情如果做的次数越多,质量就会越好,成本也会更低。所以相比自己做,通用领域选择第三方服务提供商的性价比更高。当然了,有些情况下我们会发现通用领域也不是完全通用的,在一些细小的地方不能完全满足要求。如果你找不到完全符合自己需求的产品,就争取只做二次开发。如果二次开发也不行,一定需要投入研发力量,那么尽量投入一些非核心开发力量,比如外包团队或者非资深开发人员。 同时你也不要对软件质量做过多的要求。这时候要本着能用就行的心态,研发系统的时候也按随时能替换的方式去设计。
(3)支持型领域 支撑型领域是那些用来辅助核心领域正常运行的领域。支持型领域并非核心竞争力,但是缺了之后也无法正常开展业务。比如说会计系统、市场数据系统等等,一般属于支持型领域。支持型领域和通用领域很容易搞混。支持型领域一般并不会跨行业通用,顶多只在金融行业内通用。 比如对一家做聊天论坛的公司来说,一个股票数据分析系统并没有多大帮助。因为支持型领域通用程度不大,它们的共性就会更小,因此更难找到可以替代的解决方案。如果你确定了某个系统是支持型领域,那么你可以按照这 3 个步骤来考虑:
-
和通用领域类似,首先考虑购买第三方软件。
-
如果市场上没有合适的产品,考虑能否人工处理。比如一些简单的会计科目处理或者市场数据处理,用办公软件就能达到很好的效果。金融公司不是互联网公司,只要能赚钱就行,不需要为了面子而投入稀缺的软件开发力量。
-
如果我们一定需要自研,要和对待通用领域的态度一样,能用就行,保持和核心领域低耦合,随时准备替换。
2.DDD战略设计和战术设计简介
(1)战略设计
战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。
DDD战略设计(事件风暴) == 》建立领域模型 == 》指导微服务的设计和拆分。
事件风暴是建立领域模型的主要方法,是一个从发散到收敛的过程。通常采用用例分析、场景分析、用户旅程分析,尽可能全面不遗漏地分解业务领域,并梳理领域对象之间的关系,这是一个发散的过程。事件风暴过程会产生很多的实体、命令、事件等领域对象,将这些领域对象从不同维度进行聚类,形成聚合、限界上下文等边界,建立领域模型,这是一个收敛的过程。
(2)战术设计
战术设计主要从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地。包括聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。
3.什么是通用语言
在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务含义和规则的语言就是通用语言。通用语言是团队统一的语言,可以解决交流障碍的问题。
通用语言包含属于用例场景,且能够直接反映在代码中。其中的名词可以给领域对象命名,如商品、订单等,对应实体对象;动词则表示一个动作或事件,如下单、付款等,对应领域事件。其贯穿DDD的整个设计过程。
-
事件风暴的过程中,领域专家、设计、开发一起建立领域模型,建模过程中会形成通用的业务属于和用户故事。这是团队统一语言的过程。
-
通过用户故事分析会形成一个个领域对象,并对应领域模型的业务对象,每一个业务对象和领域对象是一一映射的
-
微服务代码模型来源于领域模型,每个代码模型的代码和领域对象一一对应。
4.界限上下文(Bounded Context)
(1)界限上下文简介
限界就是领域的边界,上下文则是语义环境。用来封装通用语言和领域对象,提供上下文环境,保证在领域内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性,这个边界定义了模型的适用范围。
比如同样是一本书,在出版阶段和出售阶段所表达的概念是不同的,出版阶段我们主要关注的是出版日期,字数,出版社和印刷厂等概念,而在出售阶段我们则主要关心价格,物流和发票等概念,DDD限界上下文将这两个不同的概念区分开来。
从物理上讲,一个限界上下文最终可以是一个DLL(.NET)文件或者JAR(Java)文件,甚至可以是一个命名空间(比如Java的package)中的所有对象,而在微服务中它则可以表示一个服务。理论上限界上下文就是微服务的边界。将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。
限界上下文是微服务拆分的主要依据,如果不考虑其他外部因素,领域模型中一个限界上下文就是可以设计为一个微服务。
(2)不同类型的子域
通常来说,一个领域有且只有一个核心问题,称之为该领域的『核心子域』,没有太多个性化诉求,同时被多个子域使用的通用功能子域是通用域,还有一种功能子域是必需的,但既不是公司核心,也不含通用功能的子域,就是支撑域。
(3)界限上下文案例解析
用一个保险领域的例子来说明:保险业务有投保单、保单、批单、赔案等专业术语,分别对应保险的不同业务流程:
-
客户投保时,记录投保信息,系统对应有投保单bean;
-
缴费完成后,投保单转为保单,系统对应有保单bean,保单bean与投保单bean关联;
-
如需要修改保单信息,保单变为批单,系统对应有批单bean,批单bean与保单bean关联;
-
如果发生理赔,生成赔案,系统对应有赔案bean,赔案bean与保单bean或批单备案关联。
投保单、保单、批单、赔案等,虽然都有保单有关,但不能将保单这个术语作用在保险全业务领域,因为术语有它的边界。
首先,保险领域被拆分为投保、支付、保单管理、理赔四个子域。
子域还可以根据需要进一步拆分为子子域,到一定程度后,有些子子域的领域边界可能就变成限界上下文的边界了。子域可能包含多个限界上下文,如理赔包含报案、查看、定损等(限界上下文和理赔的子子域领域边界重合),也有可能子域本身的边界就是限界上下文边界,如投保子域。
5.上下文图(Context Map)
多个系统之间会发生关系,存在交互,这时需要将上下文进行集成。限界上下文之间集成时,我们主要关心的是领域模型和集成手段之间的关系。
比如可以使用防腐层,该层负责与外部服务提供方打交道,还负责将外部概念翻译成自己的核心领域能够理解的概念。当然,防腐层只是限界上下文之间众多集成方式的一种,另外还有共享内核、开放主机服务等。限界上下文之间的集成关系也可以理解为是领域概念在不同上下文之间的映射关系,因此,限界上下文之间的集成也称为上下文映射图,上下文图(Context Map)便是表示各个系统之间关系的总体视图。
(1)共享内核/通用域(Shared Kernel)
当不同团队开发一些紧密相关的应用程序时,团队之间需要进行协调,通常可以将两个团队共享的子集剥离出来形成共享内核(Shared Kernel)。由此可见共享内核是业务领域中公共的部分。
对于实体来说,它应该分为内部和外部两种表示,共享的应该是外部表示。
(2)客户/供应商(Customer/Supplier)
不同系统之间存在依赖关系时,下游系统依赖上游系统,下游系统是客户,上游系统是供应商,双方协定好需求,由上游系统完成模型的构建和开发,并交付给下游系统使用,之后进行联调、测试。
(3)Conformist(追随者)
如果上游系统不合作,这时候“客户/供应商”模式就不凑效了,那么下游系统只能去追随上游系统,下游系统严格遵从上游系统的模型,简化集成。简单来说就是简化上游服务的代码。
(4)防腐层(Anticorruption Layer)
调用外部接口,由于外部接口调用方式和返回值可变建议用防腐层。
如果上游系统的模型不友好,不适合下游系统的场景,但是下游系统又必须依赖于这些模型,这时候我们需要使用防腐层(Anticorruption Layer)模式将上游系统的影响降低。这种模式也是非常常见的,通常出现在系统间对接时,使用trasport+resolver的方式完成服务调用和协议转换。其中的resovler便承担了防腐的作用。
有以下几种情况会考虑引入防腐层:
-
需要将外部上下文中的模型翻译成本上下文理解的模型。
-
不同上下文之间的团队协作关系,如果是供奉者关系,建议引入防腐层,避免外部上下文变化对本上下文的侵蚀。
-
该访问本上下文使用广泛,为了避免改动影响范围过大。
比如一个抽奖系统的抽奖服务需要使用到用户城市信息服务,以用户信息防腐层举例,它以抽奖请求参数(LotteryContext)为入参,以城市信息(MtCityInfo)为输出。于是定义了用户城市信息防腐层
(5)公开主机服务(Open Host Service)
公开主机服务能够允许系统将一组服务公开出去公其他系统访问,在互通模型的同时,减少了系统间的耦合。此类模式是使用最多的。系统之间的交互通常是使用该模式来完成的,微服务架构也可以理解为此类模式的实现形式。
6.数据流转
首先领域的开放服务通过信息传输对象(DTO)来完成与外界的数据交互;在领域内部,我们通过领域对象(DO)作为领域内部的数据和行为载体;在资源库内部,我们沿袭了原有的数据库持久化对象(PO)进行数据库资源的交互。同时,DTO与DO的转换发生在领域服务内,DO与PO的转换发生在资源库内。 (注意DO和PO可以在字段相同的时候选择合二为一,但是DTO不可以)
三、DDD的战略设计
1.实体与值对象
(1)实体
领域实体。有唯一标识ID,可变的业务行为饱满实体对象,它有着自己的生命周期。因为有时需要区分是哪个实体:有两个实体,如果唯一标识不一样,那么即便实体的其他所有属性都一样,也认为他们是两个不同的实体。比如商城这一业务领域中,‘商品’就是一个业务实体,它需要有一个唯一性业务标识表征,同时他的状态和内容可以不断发生变化。
不应该给实体定义太多的属性或行为,而应该寻找关联,将属性或行为转移到其他关联的实体或值对象上。比如:Customer 实体,有一些地址信息,由于地址信息是一个完整的有业务含义的概念,所以可以定义一个 Address 对象,然后把 Customer 的地址相关的信息转移到 Address 对象上。
①业务形态
领域模型中的实体是多个属性、操作或行为的载体。
②代码形态
实体类,包含了实体的属性和方法。DDD中实体类通常采用充血模型。
③运行形态
实体以DO(领域对象)的形式存在,每个实体都有唯一的ID。对实体进行多次修改,实体仍然有相同的ID,释然是同一个实体。
④数据库形态
DDD先构建领域模型,根据场景构建实体和行为,再将实体映射到数据持久化对象。一个实体可能对应0、1或对各数据库持久化对象,大多数情况下是一对一。用户user与角色role两个持久化对象可生成权限实体,一个实体对应两个持久化对象,这是一对多的场景。
(2)domain value object领域值对象
-
页面里不可以单独显示,要放在其它实体中进行显示
-
可以没有唯一性业务标识。就以上面的地址对象 Address 为例,如果两个 Customer 的地址信息是一样的,我们就会认为这两个 Customer 的地址是同一个。用程序的方式来表达就是:如果两个对象所有属性的值都相同,我们会认为它们是同一个对象,那么就可以把这种对象设计为值对象。用于描述实体的状态的特征。
-
值对象是 不可变 的,即所有属性都是只读的,所以可以被安全的共享,当有其他地方需要用到值对象时,可以将它的副本作为参数传递。如果想要修改属性,只能返回一个新的实例
应该给值对象设计的尽量简单,不要让它引用很多其他的对象。值对象只是一个值,类似(int a = 3)中的『3』,只不过是用对象来表示。值对象虽然是只读的,是一个完整的不可分割的整体,但是可以被整个替换掉:类似(a = 4)把a的值由『3』替换为为『4』,当修改 Customer 的 Address 对象引用时,不是通过 Customer.Address.Street 这样的方式来修改属性,可以这样做:Customer.Address = new Address(…)
注:值对象应该放到实体之中,产生一对多或者一对一关系
①业务形态
值对象和实体一起构成聚合。实体是看得到、摸得到实实在在的业务对象,具有业务属性、
业务行为和业务逻辑。值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集用于描述实体的特征。
②代码形态
两种。如果值对象是单一属性,则直接定义为实体类的属性;如果值对象是属性集合,则设计为Class类,Class将具有整体概念的多个属性归集到属性集合,这样的值对象没有ID,会被实体整体引用。
③运行形态
实体实例化后的DO对象的业务属性和行为很丰富,但值对象除了数据初始化和整体替换意外,其他业务行为就很少了
值对象嵌入到实体,有两种方式。属性嵌入和序列化大对象。引用单一属性的值对象或只有一条记录的多属性值对象的实体,可以采用属性嵌入的方式。引用一条或多条记录的多属性值对象的实体,可以采用序列化大对象的方式。值对象创建后就不能修改了,只能用另一个值对象整体替换。
案例1:艺术性嵌入的方式形成的人员实体对象,地址值对象直接以属性值嵌入人员实体中。
案例2:一序列化大对象的方式形成的人员实体对象,地址值对象被序列化成大对象Json,嵌入人员实体中。
④数据库形态
DDD引入值对象是希望实现从“数据建模为中心”向“领域建模为中心”转变,减少数据库表数量和复杂的依赖关系。传统的数据建模大多是根据数据库范式设计的,每一个数据库表对应一个实体,每一个实体的属性值用单独的一列来存储,一个实体主表会对应N个实体从表。而值对象在数据库持久化方面简化了设计,它的数据库设计大多采用非数据库范式,值对象的属性值和实体的属性值保存在同一个数据库实体表中。
例如上文的人员和地址的场景,实体和数据模型设计有两种方式:第一是把值对象的所有属性都放到人员实体表中,创建人员实体,创建人员表;第二是创建人员和地址两个实体,同时创建人员和地址两张表。第一个方案会破坏地址的业务含义和概念完整性,第二个方案增加了不必要的实体和表,需要处理多个表关系,增加了数据库复杂性。
所以要在这两者之间权衡,可以综合优势。
领域建模时,可以把地址作为值对象,人员作为实体,保留了业务含义和概念完整性。而在数据建模时,可以将地址的属性值嵌入到人员实体数据库表中,只创建人员数据库表。这样兼顾了业务含义,也不增加数据库复杂度。
在领域建模时,可以将部分对象设计为值对象,保留对象的业务含义,同时减少了实体数量在数据建模时,可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。
优势和劣势:简化数据库设计,提升数据库性能。减少了实体表的数量,简单、清晰的表达业务概念。但却无法满足基于值对象的快速查询,导致搜索值对象书香之变得困难。如果实体引用的值对象过多,会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就是去了业务含义,操作也不方便。
(3)实体和值对象的关系
实体和值对象时微服务底层最基础的对象。在某些场景下时可以互换的,值对象在某些场景下有很好的价值,但并不是所有场景都适合值对象。
DDD提倡从领域模型设计出发,而不是有限设计数据模型。值对象的诞生,在一定程度上和实体是互补的。
在某些场景中,地址会被某一实体引用,它只承担描述实体的作用,并且它的值只能被整体替换,这时候就可以将地址设计为值对象,比如收货地址。而在其他业务场景中,地址会被经常修改,是作为一个独立对象存在的,这时候应该将它设计为实体,比如行政区划中的之地信息维护。
实体着重唯一性和延续性,不在意属性的变化,属性全变了,它还是原来那个它;值对象着重描述性,对属性的变化很敏感,属性变了,它就不是那个它了。
实体和值对象都是领域模型的成员,实体是业务唯一性的载体,是个富对象,包含业务逻辑和唯一标识。值对象是属性的集合,没有唯一标识,只是数据的容器,没有业务逻辑。值对象是实体的一部分,为了简化设计,将部分相关属性抽离成值对象。如果值对象变动,原来的值对象可以直接丢弃。也可以理解为值对象是当时数据的快照,只是当时的状态。值对象过多会导致业务的缺失,影响查询性能。
2.聚合
(1)聚合及聚合根简介(Aggregate,Aggregate Root)
聚合由业务和逻辑紧密关联根实体,值对象和实体组成。(不是一个具体对象)
是数据修改和持久化的基本单位,每一个聚合对应一个仓储,实现数据持久化。聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,聚合之前的边界是松耦合的。可以作为拆分微服务的最小单位,一个微服务可以包含多个聚合。
聚合在DDD分层架构中属于领域层,内部实体以充血模型实现个体业务能力。跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。
聚合:DDD中实体一般对应业务对象,具有业务属性和业务行为;而值对象主要是属性集合,对实体的状态和特征进行描述。但实体和值对象都是个体化的对象,他们的行为表现出来的是个体的能力,让实体和值对象协同工作的组织就是聚合。
聚合根:聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。一个聚合内只有一个聚合根,聚合根在聚合内对实体和值对象采用直接引用的方式进行组织和协调,聚合与聚合根之间通过ID关联的方式实现聚合之间的协同。
如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。作为实体,拥有实体的属性和业务行为,实现自身业务逻辑。作为聚合管理者,负责协调实体和值对象按照固定业务规则协同完成业务逻辑。在聚合之间,它还是聚合对外的接口人,以聚合根ID关联的方式接受外部任务的请求,在上下文内实现聚合之间的业务协同。即聚合之间通过聚合根ID关联引用。访问其他聚合的实体,需要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内的实体。根如果没有了,那么聚合中的其他对象也将不存在。
比如收货地址中,收货人是该聚合的根,其他的都是内部对象,如果外部需要用户地址,拷贝一份传递出去即可。显而易见,用户如果不存在,其他信息均无意义。
比如订单操作中,订单表就是一个聚合根,围绕订单进行的其它操作和订单表的操作构成了一个聚合的关系。
3.aggregate聚合的设计
-
尽量设计小的aggregate,大aggregate会造成资源浪费,包含的实体过多,实体之间的管理就会很复杂,高频操作会出现并发冲突或数据库锁,导致可用性变差,只有在确定数据一致性为主导的时才考虑设计大aggregate。
-
在一致性边界内建模真正的不变条件。聚合用来封装真正的不变性,而不是简单地将对象组合在一起。边界外的东西都与该聚合无关,这也是聚合能实现业务高内聚的原因。
-
通过唯一标识引用其他聚合。聚合之间通过关联外部聚合根ID的方式引用,而不是直接对象引用。边界清晰,不会增加耦合度
-
在边界之外使用最终一致性。聚合内数据强一致性,聚合之间数据最终一致性。一次事务最多只能更改一个聚合的状态。若一次业务操作设计多个聚合状态的修改,应采用领域事件的方式异步修改相关的聚合,实现聚合之间解耦。
-
通过应用分层实现跨聚合的服务调用。应避免跨聚合的领域服务调用和跨聚合的数据库表关联。
假设我们处理一个教室物品管理的问题,教室里有椅子,桌子和黑板这些物品。假设这些物品需要维护,要记录各个物品是在何时维护的。 比较直观的方案当然是把教室当作一个aggregate,里边有它的物品。
public class Classroom {
private ClassroomId classroomId;
private String classRoomNo;
private List<Item> items;
public Item add(Item item) throws ItemLimitExceededException {
if(items.size() == 50) {
throw new ItemLimitExceededException();
}
item.add(item);
}
public Item findItem(ItemId itemId){
items.toStream().filter(item -> item.getItemId() == itemId).findFirst().getOrElse( null );
}
}
物品类(椅子,桌子,黑板等)
@Getter
public class Item {
private ItemId itemId;
private ItemType itemType;
private Date maintenanceDate;
public void maintain(){
maintenanceDate = DateUtils.now(); // 更新检测日
}
}
public enum ItemType {
Chair, Desk, Blackboard
}
当我们用这种方式实现时,我们获取Classroom的引用时,必然会把它所包含的物品一起获取。 当时这个问题可以通过懒加载的方式解决,而其主要导致的问题是当要处理chair时,首先需要获得classroom 然后再获得Item最后才能获得chair,而在获得chair这个过程中还获得了教室中包含的其它物品,这样就导致了资源的浪费。
另外,按照这样的做法,如果你对某一个教室进行更新时,还会更新它所包含的物品。这样的更新范围较大,当这个系统需要管理的东西变得十分庞大,并发处理很多时,容易导致锁问题的出现,造成更新的失败。
4.领域模型的战略模式设计
领域模型是将数据和行为封装在一起,并与现实世界的业务对象相映射。各类具备明确的职责划分,使得逻辑分散到合适对象中,这样的对象就是“充血模型” 。
在具体实践中,我们需要明确一个概念,就是领域模型是有状态的,他代表一个实际存在的事物。
(1) 领域模型(Domain Model)
由此可见,领域驱动设计的核心是建立正确的领域模型。领域模型具有以下特点:
-
对具有某个边界的领域的一个抽象,反映了领域内用户 业务需求的本质 。它属于『解决问题空间』。领域模型是有边界的,只反应了我们在领域内所关注的部分,包括 实体概念(如:货物,书本,应聘记录,地址等),以及 过程概念(如:资金转账等);
-
提高软件的 可维护性,业务可理解性以及可重用性。领域模型确保了我们的软件的业务逻辑都在一个模型中,帮助开发人员相对平滑地将领域知识转化为软件构造;
-
重要性:领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化;
(2)建模思考的问题:用户需求
设计领域模型时不能以用户为出发点去思考问题,不能老想着用户会对系统做什么;而应该从一个客观的角度,根据用户需求挖掘出领域内的相关事物,思考这些事物的本质关联及其变化规律作为出发点去思考问题。
领域模型是 排除了人之外的客观世界模型 ,包含了人所扮演的参与者角色。但是一般情况下不要让参与者角色在领域模型中占据主要位置,否则各个系统的领域模型将变得没有差别。例如:
-
论坛中如果以人为主导,那么领域模型就是:人发帖,人回帖,人结贴,等等;
-
货物托运系统中如果以人为主导,就变成了:托运人托运货物,收货人收货物,付款人付款,等等;
描述不应从用户的角度出发,而是以领域内的相关事物为出发点,考虑这些事物的本质关联及其变化规律的:
-
以货物为中心,把客户看成是货物在某个场景中可能会涉及到的关联角色,如货物会涉及到托运人、收货人、付款人;
-
货物有一个确定的目标,货物会经过一系列的运输动作到达目的地。
(3)理解领域知识是基础
虽然知道了系统主要解决什么问题,但是就这样我们还无法开始进行实际的需求分析和模型设计,我们还必须将我们的问题进行拆分,需求进行细化。有些时候,需求方,即提出问题的人,很可能自己不清楚具体想要什么。他只知道一个概念,一个大的目标。比如他只知道要做一个股票交易系统,一个电商平台等。但是他不清楚这些系统应该具体做成什么样子。这个时候领域专家就应该表达出系统该做成什么样子。如果你不了解领域知识,就算你领域建模的能力再强,各种技术架构能力再强也是使不上力。领域专家不是某个固定的角色,而是某一类人,这类人对这个领域非常了解。
(4)拆分领域
有时一个领域往往太复杂,涉及到的领域概念、业务规则、交互流程太多,导致我们没办法直接针对这个大的领域进行领域建模。所以,我们需要将领域进行拆分,本质上就是把大问题拆分为小问题,然后各个击破的思路。然后既然把一个大的领域划分为了多个小的领域(子域),那最关键的就是要理清每个子域的边界;然后要搞清楚哪些子域是核心子域,哪些是非核心子域,哪些是公共支撑子域;然后,还要思考子域之间的联系是什么。子域的划分应该从业务相关性的角度去思考,也就是我们平时说的按业务功能为出发点进行划分。还是拿经典的电商系统来分析,通常一个电商系统都会包含好几个大块,比如:
-
会员中心:负责用户账号登录、用户信息的管理;
-
商品中心:负责商品的展示、导航、维护;
-
订单中心:负责订单的生成和生命周期管理;
-
交易中心:负责交易相关的业务;
-
库存中心:负责维护商品的库存;
-
促销中心:负责各种促销活动的支持;
上面这个电商领域的划分看起来很简单,如果我们遇到一个冷门的领域,就没办法这么容易的去划分子域了。这就需要我们先去努力理解领域内的知识。不过并不是所有的系统都需要划分子域的,有些系统只是解决一个小问题,这个问题不复杂,可能只有一两个核心概念。所以,这种系统完全不需要再划分子域。但不是绝对的,当一个领域,我们的关注点越来越多,每个关注点我们关注的信息越来越多的时候,我们会不由自主的去进一步的划分子域。比如,也许我们一开始将商品和商品的库存都放在商品中心里,但是后来由于库存的维护越来越复杂,导致揉在一起对我们的系统维护带来一定的困难时,我们就会考虑将两者进行拆分,这个就是所谓的业务垂直分割。
(5)细化子域(需求细化)
-
梳理领域概念:梳理出领域内我们关注的概念、概念的关系,并统一交流词汇,形成统一语言;
-
梳理业务规则:梳理出领域内我们关注的各种业务规则,DDD中叫不变性(invariants),比如唯一性规则,余额不能小于零等;
-
梳理业务场景:梳理出领域内的核心业务场景,比如电商平台中的加入购物车、提交订单、发起付款等核心业务场景;
-
梳理业务流程:梳理出领域内的关键业务流程,比如订单处理流程,退款流程等;
从上面这4个方面,我们从领域概念、业务规则、交互场景、业务流程等维度梳理了我们到底要什么,整理了整个系统应该具备的功能。
(5)业务建模战术(用一句话描述业务场景)
这句话需要一个完整的句子,有主谓宾状,可能还有定语。主语和宾语往往就是我们要找的实体/值对象,谓语便是主语对应实体/值对象的行为方法,状语就是这个case下的业务规则,往往需要归类到前面的实体行为方法中,至于定语,也会是一些业务规则,同样要内聚到主语对应的实体中。
举个例子,在社区发帖的业务场景下,我们尝试使用一句话描述:帖子作者只能在其已经加入了的某个圈子下才能发布帖子。对照上面的方法,那么“帖子作者”是主语,“帖子”是宾语,“发布”是谓语,“只能在其已经加入了的圈子下”是状语。这样我们可以得到“帖子作者”、“帖子”两个实体,得到“帖子作者”有一个“发布帖子”的行为方法,得到一条业务规则:帖子作者发布帖子的前提是加入对应的圈子。
5.领域建模设计步骤
DDD领域建模通常采用事件风暴,使用用例分析、场景分析、用户需求分析等方法,通过头脑风暴列出所有可能的业务行为和事件,然后找出产生这些行为的领域对象,并梳理对象之间的关系,找出聚合根,找出与聚合根业务紧密关联的实体和值对象,再将聚合根、实体和值对象组合,构建聚合。以保险的投保业务场景为例:
领域建模的主要目的是捕捉业务知识,形成统一语言,沉淀领域模型。好的领域建模就意味着对业务要有深刻的理解,能够洞察问题本质。领域建模的产出物一般有以下内容:
-
领域模型:包含领域对象、属性、关系、行为、边界范围等各个方面,用于描述业务的本质,这也是最重要的产出物。
-
用例图:用于明确系统的功能。
-
数据模型:描述系统的数据结构和关系,包括实体关系模型、关系数据库模型等。
-
状态图:用于描述系统各个状态及其转移条件。
-
活动图:用于描述系统流程中的各个活动及其关系。
-
序列图:描述系统中各个对象之间的交互过程和消息传递序列。
-
架构模型:包含系统的物理和逻辑结构,包括组件、模块、接口等。
(1)事件风暴
事件风暴是一项团队活动,领域专家与项目团队通过头脑风暴的形式,罗列出领域中所有的领域事件,整合之后形成最终的领域事件集合,然后对每一个事件,标注出导致该事件的命令,再为每一个事件标注出命令发起方的角色。命令可以是用户发起,也可以是第三方系统调用或者定时器触发等,最后对事件进行分类,整理出实体、聚合、聚合根以及限界上下文。
事件风暴设计过程中可以用一些表格,来记录事件风暴和微服务设计过程中从命令和事件中提取产生的领域对象及其属性。比如领域对象在DDD分层架构中的位置、属性、依赖关系以及与代码模型对象的映射关系等。
DDD分析和设计的每一个环节都保证限界上下文内术语的统一,在代码模型设计的时候就要建立领域对象和代码对象的一一映射,保证业务模型和代码模型的一致,实现业务语言与代码语言的统一。
在领域建模的过程中,需要重点关注这类业务的语言和行为。比如某些业务动作或行为(事件)是否会触发下一个业务动作,这个动作(事件)的输入和输出是什么?是谁(实体)发出的什么动作(命令),触发了这个动作(事件)…可以从这些暗藏的词汇中,分析出领域模型中的事件、命令和实体等领域对象。
事件风暴通过事件、命令与策略之间的响应关系来组织逻辑。它定义了一套彩色贴纸的”语法”: 不同颜色的贴纸都有定义。浅黄色代表角色(Actor)、蓝色表示命令(Command)、粉色代表业务规则(Policy)、紫色代表系统(System)、橙色代表事件(Event), 绿色表示阅读模型(Read Model)、红色代表热点问题(HotSpot)。
每种语法的具体含义如下:
-
行动者(Actors)是系统的使用者。这里使用者是一个相对模糊的概念,可能是现实中的人也可能是别的系统;
-
命令(Command)是由行动者发起的行为。它代表了某种决定,通常是事件的起因,也称作行动者触发命令(AIC,Actor Initiated Command);
-
事件(Event)就是我们前文讨论过的事件;
-
系统(System)指代的是不需要了解细节的三方系统。因为不需要了解细节,所以我们可以将它们看作一个整体;
-
阅读模型(Read Model)用以支撑决策的信息。通常与界面布局有关;
-
策略(Policy)是对于事件的响应,通过策略可以触发新的命令,由策略触发的命令,被称作系统触发命令(SIC,System Initiated Command)。
-
热点问题(HotSpot)是业务痛点,瓶颈,模糊点。如果有争论,可以将双方观点用热点问题的形式记录。
①准备工作
第一.准备物料:彩色贴纸、笔纸、一个足够大的房间等。房间里不要有椅子,因为在事件风暴过程中,我们希望大家都全神贯注的投入,而不是坐在椅子上开始放松。
第二.邀请正确的人:有问题的人和有答案的人。程序员、交互设计师、测试等都是有问题的人,需要通过事件风暴理解业务和产品;有答案的人通常是用户、业务或产品,他们通常能回答业务的背景,诉求和目标。
②开场介绍
在事件风暴中,有一个特殊角色是主持人,一般也是事件风暴的组织者。主持人有几个重要职责,主持事件风暴、保持参与者的专注、通过提问驱动交流、总结提炼事件风暴建模成果。
在事件风暴正式开始前,由主持人介绍事件风暴是什么、有什么好处以及彩色贴纸的用法。然后介绍本次事件风暴讨论的范围和目标。
例如,此次事件风暴讨论“智慧课堂”中用户订阅专栏的场景,目标是理清从用户发起订阅到用户查看订阅专栏的整个的业务流程。
③事件风暴的方式沟通业务
第一步:梳理事件(橙色贴纸)
事件是已发生且重要的事情。事件必须是既成事实,且业务关注的事情。通常主持人会先准备第一个事件(可以是系统中任一事件),然后把它贴到墙上。
假设第一个事件是:专栏已订阅。接下来主持人通过提问引导大家找到更多的事件:
事件发生前有哪些事件?(“专栏已订阅”前须有“订单已支付”事件)
事件发生后下一个事件是什么?
提问会引导参与事件风暴的同学将新发现的事件不断补充到墙上。事件要保持整体的时间顺序:先发生的事情贴在左边,后发生的事情在右边。通常大家容易关注系统的正常流程,也就是Happy path。这时候主持人需要引导大家关注业务的非正常流程Unhappy path。边界条件,异常情况通常是业务复杂性的重要原因,也是非常容易被忽视的部分。
事件一定会发生吗?(订单一定可以创建成功吗?不是,贴上“订单创建失败”事件)
追问unhappy path梳理出业务的完整视图,当大家发现新事件的速度接近停滞的时候,就应进入梳理业务规则的阶段了。
第二步:业务规则(粉色贴纸)
业务规则或者业务逻辑,是业务中最重要的部分,主持人会提出以下问题:
事件是否一定成功?如果不是,那么成功的前提条件是什么?
该事件是否会导致其他事件的发生?
例如“订单已创建”事件的业务逻辑
-
订单已创建的前提条件是专栏可订阅,同时用户未订阅过该专栏。
-
订单创建后,会导致发起支付。
第三步:行动者(浅黄色贴纸),命令(蓝色贴纸)、阅读模型(绿色贴纸)和系统(紫色贴纸)
主持人通过问题引导:
是什么触发了事件,是命令还是规则
是谁执行了动作,是人还是系统
做出动作前,用户需要获取到哪些信息
通过类似上面的问题,逐步引导大家找到Actor, Command, Read Model
第四步:热点问题(红色贴纸)
业务痛点、瓶颈、模糊点可以用红色贴纸记录,这些问题需要业务带回去讨论确定清楚,不要在事件风暴中尝试解决所有的hotspot。
第五步:故事串讲
邀请一名现场成员,按事件发生的时间顺序串讲业务,过程中,听众注意到不一致的地方,提出问题;大家一起讨论,调整相关的事件、逻辑来达成一致。
第六步:产出架构
通过事件风暴,业务流程和处理逻辑应该已经很清楚了,接下来就由架构师产出对应的架构。可以依据事件风暴产出领域模型、用例图、状态图、活动图、序列图等关键架构交付物。
(2)从众多实体中选出适合作为对象管理者的根实体
可以根据以下场景分析:是否有独立的生命周期?是否有全局唯一ID?是否可以创建或修改其他对象?是否有专门的模块来管这个实体。图中的聚合根分别是投保单和客户实体;
根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密以来的实体和值对象。构建出1个包含聚合根(唯一)、多个实体和值对象的对象集合,这个集合就是聚合。
(3)画出对象的引用和依赖模型
在聚合内根据聚合根、实体和值对象的依赖关系,画出对象的引用和依赖模型。上图中投保人和被保人是通过关联客户ID从客户聚合中获取的,在投保聚合中他们是投保单的值对象,是客户的冗余数据,即使未来客户聚合的数据发生了改变,也不影响投保单的值对象数据。可以看出实体之间的引用关系,比如在投保聚合里投保单集合跟引用了报价单实体,报价单实体则引用了报价规则子实体。
多个聚合根据业务语义和上下文一起划分到同一个限界上下文中。
至此,建立了领域模型,划定了业务领域的边界,建立了通用语言和限界上下文,确定了领域模型中各个领域对象的关系。业务端领域模型和设计基本就完成了,同时也确定了应用端的微服务边界。
在从业务模型向服务落地的过程中,也就是战略设计向战术设计的实施过程中,会将领域模型中的领域对象与代码模型中的代码对象建立映射关系,将业务架构和系统架构进行绑定。当为了响应业务变化调整业务架构和领域模型时,系统架构也会同时发生调整,同步建立新的映射关系。
(4)微服务拆分与设计
原则上一个领域模型就可以设计为一个微服务,但由于领域建模时只考虑了业务因素,没有考虑微服务落地时的技术、团队以及运行环境等非业务因素,因此在微服务拆分与设计时,不能简单地将领域模型作为拆分微服务的唯一标准,它只能作为微服务拆分的一个重要依据。
微服务的设计还需要考虑服务的粒度、分层、边界划分、依赖关系和集成关系。除了考虑业务职责单一外,还需要考虑将敏态与稳态业务的分离、非功能性需求(如弹性伸缩要求、安全性等要求)、团队组织和沟通效率、软件包大小以及技术异构等非业务因素。
6.战略设计案例
假设你有一天成为了有钱人,需要有人帮你打点一下财产。这时候金融公司会给你提供资管服务,替你理财。理财产品有很多种,其中资产数量最大、收益最高、风险也最高的一类是衍生品。在这里我们看看衍生品管理系统应该怎么分析。 理财产品的管理分两步。先要购买合适的金融产品,也叫交易前,或者投前。买好之后就需要管理,在合适的时候买进卖出。这一步也叫交易后,或者投后。我们先来看看投前的过程涉及到的系统应该怎么分析。 首先,你需要在系统中记录有兴趣购买的衍生品。因为衍生品是一种有完整生命周期的金融合同,所以系统需要有一个金融合同的生命周期管理系统。其次,你在挑选金融产品的时候需要知道这个产品的价格,如果价格低就买。这就是定价和报价环节。这个环节开始的时候,你需要对合同价格有自己的判断,这就要用到定价系统。接着就到了合同签订和打印的步骤。金融合同的签订其实是一个交易过程,就需要有交易系统。衍生品交易涉及到的金额特别巨大,因此很多人都不太信任电子合同。合同签订之后还需要打印存档,这就需要有打印系统。投前的步骤在合同确认之后就结束了。接下来就需要你自己管理合同了。这时一般要注意这几个事项。
-
第一,金融合同签订以后,买卖双方需要履行合同义务,也就是按照合同声明的金额进行资金往来,因此需要有支付系统和会计系统。
-
第二,资金转账通常不能在节假日进行,所以需要日期系统来通知系统自动调整日期。
-
第三,你需要随时知道自己的资产有多少,风险有多大,这里需要有风险计算系统。 那么,一个简单的衍生品管理系统就成型了,它需要下面这些不同的领域系统:
先来看看哪几个是核心领域。你做资产管理是为了获得合理的收益,所以核心业务应该和金融产品的收益相关。这里我们发现有两个系统组件比较相关,分别是定价和风险计算系统,所以这两个属于核心领域。至于打印、支付、会计、日期变更等都是常见功能。除了金融行业,很多其他行业也有,所以这些都是通用功能,属于通用领域。但是相对而言,交易、报价和生命周期管理没那么通用,但是也不属于核心竞争力,因此这些都属于支持型领域。