DDD领域驱动设计初步认识

    2004年Eric Evans 发表《领域驱动设计——软件核心复杂性应对之道》(Domain-Driven Design –Tackling Complexity in the Heart of Software),简称Evans DDD,领域驱动设计思想进入软件开发者的视野。领域驱动设计分为两个阶段:

  • 1、以一种领域专家、设计人员、开发人员都能理解的通用语言作为相互交流的工具,在交流的过程中发现领域概念,然后将这些概念设计成一个领域模型;

  • 2、由领域模型驱动软件设计,用代码来实现该领域模型;

    简单地说,软件开发不是一蹴而就的事情,我们不可能在不了解产品(或行业领域)的前提下进行软件开发,在开发前,通常需要进行大量的业务知识梳理,而后到达软件设计的层面,最后才是开发。而在业务知识梳理的过程中,我们必然会形成某个领域知识,根据领域知识来一步步驱动软件设计,就是领域驱动设计的基本概念。而领域驱动设计的核心就在于建立正确的领域驱动模型。

    介绍DDD之前,我们先看一下传统架构设计和领域驱动设计的区别(贫血模式,充血模式):

    传统的架构设计是用的贫血模式,是指领域对象里只有get和set方法,包含状态(属性),不包含行为(方法),采用这种设计时,需要分离出DB层,专门用于数据库操作,数据和业务逻辑被分割到不同的类中。

    优点:业务比较清晰简单,系统的层次结构清楚,各层之间单向依赖。

    缺点:将数据与操作分离,破坏了面向对象的封装特性,随着业务的越来越负责,系统也越来越冗杂,最后就有可能导致系统重构

    DDD是基于充血模型,将大多数业务逻辑和持久化放在领域对象中,业务逻辑只是完成对业务逻辑的封装、事务和权限等的处理。充血模型更加倾向于面向对象的设计风格

    优点:面向对象,将业务抽离和封装,通过领域的概念来划分,业务逻辑不再集中在几个大型的类上,而是由大量相对小的领域对象(类)组成,这些类具备自己的状态和行为,每个类是相对完整的独立体,并与现实领域的业务对象映射

    缺点:学习成本比较高,很多概念比较抽象,如何划分,怎么剥离业务逻辑等前期讨论准备工作较为复杂。

    当然,不是说所有的系统都适合用DDD,如果系统比较简单明了,那么完全可以用传统架构设计来代替,绝对不能说为了DDD而去DDD。

    DDD的一些概念比较抽象,接下来我用自己的理解来举例解释一下DDD中的概念

    1.领域:抽象概念上来讲就是一种特定范围内的区域,用来解决一些本质的问题。

    领域是有大小之分的,可以进一步的划分为不同的子域,每一个子域对应一个更小的问题或更小的业务内容,子域可以继续划分为更小的子域,每一个细分的子域都是一个知识体系,把所有的子域都研究透彻了,那么整个系统的业务也就明白了。

    子域又可以根据自身的重要性和功能属性划分三类:核心域,通用域,支撑域

    2. 核心域:决定产品和公司核心竞争力的子领域。

    3.通用域:同时被其他多个子领域使用的通用功能的子领域,为整个业务系统提供通用服务。

    4.支撑域:不包含决定产品核心竞争力的功能,不包含通用功能的子领域,具有企业业务特性,不具有通用性,业务系统中某些非核心的重要业务,用来支撑和完善整个系统

    以我们的灵活用工系统为例,来讲一下我理解的这些概念:

    灵活用工系统我先简单的划分一下,可以分为     任务,认证,合同,结算,提现等几个方面。

    首先,我们应该明确一个子域中只有一个核心域,如果有多个核心域,证明这不是一个领域,还要继续划分。

   我们现在把灵活用工系统看作是一个子域,首先确定我们的核心域,应该不需要我多说,肯定是提现,我们灵活用工的核心业务是提现。

   那么在这个灵活用工这个领域中,核心域先确定为提现,然后认证,合同这些可以划分为通用域,因为在我们的发薪系统中也会用到。剩下的还有任务和提现,这两块是灵活用工独有的,具有企业业余特性的且不具备通用性的,我们就可以划分为支撑域。

    5.限界上下文:定义领域边界,是一个显式的边界,领域模型便存在于这个边界之内。创建边界的原因在于,每一个模型概念,包括它的属性和操作,在边界之内都具有特殊的含义。而领域模型需要准确的反映通用语言。

    我先拿网上找的一个示例举例:

    “我想静静。”
    这个句子一般是想表达“我想静一静”的意思。但是我们却把它玩笑成“静静是谁?”。
    可见心思不同的语境中呈现出来的是完全不同的意思。

    再拿一个我们系统中的示例来举例

    我们在salary发薪平台发薪之前需要上传表格进行审核,我们在灵活用工上传结算额的时候也需要上传表格,同样都是表格,但是在salary和灵活用工平台代表的完全是两种不同的意思,都是需要通过和salary发薪平台或者灵活用工平台这两个领域结合起来看才能明确。

    限界上下文是微服务拆分和设计的主要边界依据,当然微服务还有其他的划分边界依据,需要综合考虑;我们将限界上下文内的领域模型映射到微服务就完成了从问题域到软件的解决方案。

    6.通用语言:通过团队交流达成共识的,能够简单,清晰,准确的描述业务含义和规则的语言,可以解决交流障碍,确保业务的正确表达

    我们整个的系统设计的参与者其实不仅仅包括了我们研发部门,肯定还包括了市场产品部门以及互联网产品部门。大家如果是各自交流的时候,就有可能导致沟通不及时,错误理解。不仅仅是不同部门之间的交流,甚至是仅仅研发部门的小伙伴共同开发,也有可能因为个人的开发习惯,开发思想等导致实际做出来的系统和之前规划的相差甚远。

   所以,我们应该将模型做为语言的中心,确保团队在所有交流活动和代码中坚持使用这种语言。在画图,写东西特别是讲话的时候也要使用这种语言。通过尝试不同的表示方法来消除难点,然后重构代码,并对类,方法和模块公共命名,以便和新模型一致。解决团队成员之间交流的术语混淆问题,就像我们对普通词汇形成一个公认的理解一样。

    7.实体和值对象(个体化的对象):

    7.1:实体:

    《领域驱动设计:软件核心复杂性应对之道》 中对实体的解释:

  • 实体(Entity,又称为Reference Object) 很多对象不是通过他们的属性定义的,而是通过一连串的连续事件和标识定义的。
  • 主要由标识定义的对象被称为ENTITY。

    用通俗一点的话来讲,实体就是具有唯一标示并且可以持续变化的事物/对象。同我们传统架构上的实体类高度相似,但是不完全等同。

    7.2:值对象:

 《领域驱动设计:软件核心复杂性应对之道》 中对值对象的解释:

      当我们只关心一个模型元素的属性时,应该将其归类到value object中。我们应该使这个模型元素表示出属性的意义,并为它提供相关功能。Value object应该是不可变的。不要为它分配任何标识,而且不要把它弄的和entity一样复杂。

    我以我们项目中的PaymentAccount和PaymentAccountMybank来举个例子

    public class PaymentAccount {

               private Long id;

               private Long companyId;

               private String alias;

               ......

    }

    public class PaymentAccountMyBank {

               private Long id;

               private Long cid;

               private Long paymentAccountId;

               private String channelCode;

               private String settlementAccount;

               ......

}

    paymentAccount中是一些通用实体户的配置,而paymentAccountMyBank中是针对网商的一些配置。我们目前的数据库中是分了两张表的,但是其实完全是可以用一张表来定义的。我们先忘了数据库原来的设计,重新定一个新的表。

    public calse PaymentAccountNew {

               private Long id;

               private Long companyId;

               private String alias;

               ......

               //先去除多余的属性

               private String channelCode;

               private String settlementAccount;

               ......     

}

    我们得到了一个新的PaymentAccountNew对象,这个PaymentAccountNew就可以看作是一个实体对象。

    然后我们发现,原先的paymentAccountMyBank对象中的属性都是属于网商的相关配置,我们就可以定义一个值对象

    public class PaymentAccountMyBankNew {

               private String channelCode;

               private String settlementAccount;

               ......

}

    那么现在现在原来的PaymentAccountNew实体中的属性就变成了

    public class PaymentAccountNew {

               private Long id;

               private Long companyId;

               private String alias;

               ......

               private PaymentAccountMyBankNew paymentaccountMyBankNew;

}

    我们可以把实体对象理解为一种描述了某种特征或属性且具有唯一标示的对象,相对应的,值对象就是一种描述了某种特征或属性但是没有标示概念的对象。值对象不可能单独存在,一定是依附与实体对象的,是实体属性的一部分。

    实体对象和值对象的定义不是绝对的,是和领域息息相关的。你所判定的实体一定是基于领域当前环境(上下文)的。脱离了该环境之后,一切都将存在变数。同样的事物(对象),在当前环境需要一个唯一标识来识别它,而在另一个环境中可能这个唯一标识对它来说是没有意义的,则实体就有可能成为了值对象。

   值对象可以简化数据库设计,提升性能。    

    实体一般对应业务对象,具有业务属性和业务行为,值对象主要是属性集合,描述实体的状态和特征,但都只是个体化对象,其行为表现出的是个体能力,所以DDD中还定义了聚合和聚合根的概念。      

    8.聚合和聚合根

    8.1:聚合是一种强关联关系,将相关联的领域对象进行显示的分组,来表达整体的概念。

    识别聚合经过理论和实际的项目开发,我认为应该从以下几个方面进行聚合划分

    1.哪些实体或值对象在一起才能够有效的表达一个领域概念。

    2.对象之间是否必须保持一些固定的规则。

    3.聚合不要设计太大,否则会有性能问题以及业务规则一致性的问题。

    4.聚合中的实体和值对象应该具有相同的生命周期,并应该属于一个业务场景。

    每个聚合都会有一个聚合根和聚合的边界上下文,边界定义了在一个聚合里面内部应该有哪些实体,哪些子实体对象。定义边界上下文的原因是我们期望对一个聚合的访问是通过聚合根点进行的,聚合里面的子实体对外界是完全封闭的。对于外部对象不应该去访问到一个聚合边界里面的子实体。

    8.2:聚合根

    聚合根的目的是为了避免由于复杂数据模型缺少统一的业务规则控制,导致聚合和实体之间的数据不一致的问题

    1.一个聚合只有一个聚合根,聚合根是可以独立存在的,聚合中其他实体或值对象依赖与聚合根。

    2.只有聚合根才能被外部访问到,聚合根维护聚合的内部一致性。

    3.聚合根在聚合之内采用引用依赖的方式对实体和值对象进行组织和协调

 

聚合划分的原则 聚合作为 DDD 的对象体系中的一层,也同样应该遵循高内聚、低耦合的原则。本文认为,聚合边界内的对象应满足如下的启发式规则:

1.生命周期一致性

2.问题域一致性

3.场景一致性

    场景(scenario)是业务用例的具体化描述,反应了用户使用系统达成业务目标的方式。我们可以观察这些场景中涉及的领域对象操作,如对领域对象的查看、修改等。场景操作频率的一致性是同一聚合内部对象的一个关键表征。经常被同时操作的对象,它们属于同一个聚合。而那些极少被同时关注的对象,不应该划为一个聚合。(这边可以举例任务+合同+工商注册)

4.聚合内的元素尽可能少

聚合根可以举例帖子和回复,还有自己项目的order和order_item

   

参考博客:https://www.sohu.com/a/359175454_468635

 

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值