美文网首页
[DDD]领域驱动设计概要

[DDD]领域驱动设计概要

作者: 先生zeng | 来源:发表于2020-03-11 15:41 被阅读0次

    一、定义

    DDD是Domain Driven Design(领域驱动设计)的简称,这是一种软件设计和开发的方法论,一般适用于复杂业务领域软件设计和开发。

    过去系统分析和系统设计都是分离的,正如我们国家“系统分析师” 和“系统设计师” 两种职称考试一样,这样割裂的结果导致,需求分析的结果无法直接进行设计编程,而能够进行编程运行的代码却扭曲需求,导致客户运行软件后才发现很多功能不是自己想要的,而且软件不能快速跟随需求变化。

    DDD则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。

    服务器后端发展三个阶段:

    1.UI+DataBase的两层架构,这种面向数据库的架构(上图table module )没有灵活性。
    2.UI+Service+DataBase的多层SOA架构,这种服务+表模型的架构易使服务变得囊肿,难于维护拓展,伸缩性能差。见这里讨论Spring Web 应用的最大败笔.

    1. DDD+SOA微服务的事件驱动的CQRS读写分离架构,应付复杂业务逻辑,以聚合模型替代数据表模型,以并发的事件驱动替代串联的消息驱动。真正实现以业务实体为核心的灵活拓展。

    比赛Match为案例,提倡充血模型,实际就是让过去被肢解被黑crack的业务模型回归正常,当然这也会被一些先入为主或被洗过脑的程序员看成反而不正常,这更是极大可悲之处。看到领域模型代码,就看到业务需求,没有翻译没有转换,保证软件真正实现“拷贝不走样”。

    DDD最大的好处是:接触到需求第一步就是考虑领域模型,而不是将其切割成数据和行为,然后数据用数据库实现,行为使用服务实现,最后造成需求的首肢分离。DDD让你首先考虑的是业务语言,而不是数据。重点不同导致编程世界观不同。

    DDD认为很多原因造成软件的复杂性,我们不可能避免这些复杂性,能做的是对复杂的问题进行控制。而一个好的领域模型是控制复杂问题的关键。领域模型的价值在于提供一种通用的语言,使得领域专家和软件技术人员联系在一起,沟通无歧义。

    DDD落地实现离不开Clean架构、六边形架构CQRS、Event Source几大大相关领域。

    二. 专业名词

    先介绍一些专业性名词,对于后面的核心点比较好理解。

    Bounded context:边界上下文

     划分领域边界,边界内领域模型保持一致,强调内聚,并与边界外的领域模型解耦。
    

    Entity:领域实体

    有唯一标识,可变的业务实体对象,它有着自己的生命周期。比如User、帖子等。
    

    Value Object:领域值对象

    没有唯一业务标识,通常依附于其他领域实体,值对象的内容不可变,要么被整体替换。如:用户点赞行为等。
    

    Aggregate:聚合

    是一组业务关联度很强的实体/值对象集合,每个聚合都有一个根实体(Root Entity),通过根实体可以路由到整个聚合。
    

    Domain Event:领域事件

    领域中发生的异步处理事件、异步消息通知等,比如:异步写入的登录历史记录。通常借助消息队列实现。

    Domain Service:领域服务

    当某些业务行为无法归类到某一个Entity/Value Object时,我们便可以创建领域服务来完成。比如:账户转账场景,涉及到两个不同的Account实体,再比如社区的敏感词过滤场景,帖子可以用,评论亦可以用,因此可以抽离到ContentFilter中完成。

    Repository:仓库

    严格意义上将仓库是基础设施层的东西,但是为了保持领域模型的整体性,我们将仓库的接口定义放到领域中,这样可以在领域中约束实体/值对象的增删改查接口,同时还可以方便地完成仓库的内存形式实现,使得领域模型弱依赖于持久化层。这一点在书中被成为‘依赖倒置’(参考《实现领域驱动设计》P372)。

    Factory:领域对象工厂

    用于复杂领域对象的创建/重建。重建是指通过respostory加载持久化对象后,重建领域对象。

    聚合(Aggregate)

    聚合主要用来封装对象的内部结构.其作用就是将关联的对象聚合在一起,由一个对象对外暴露遍历接口.这样能够降低对这组关联对象的维护的复杂度.每个聚合都要有一个根(root)和一个边界(boundary).

    为了实现概念上的聚合根,需要对所有事物应用一组规则.

    1.根实体具有全局标识,它最终负责检查固定规则.
    2.根实体具有全局标识.边界内的实体具有本地标识,这些标识只有在聚合根内部才唯一.
    3.聚合外部的对象不能引用除根实体之外的任何内部对象.根实体可以把对内部实体的引用传递给它们,
    但这些对象只能临时使用这些引用,而不能保持引用.根可以把值对象的副本传递给另一个对象,
    4.而不必关心它发生什么变化,因为它只是一个值对象,不再与聚合根有任何关联.
    5.作为上一条规则的推论,只有聚合的根才能直接通过数据库查询获取.所有其他对象必须通过关联的遍历才能找到.
    6.聚合内部的对象可以保持对其他聚合根的引用.
    7.删除操作必须一次删除聚合之内的全部对象.
    8.当提交对聚合边界内部的任何对象的修改时,整个聚合中的所有固定规则都必须被满足.
    

    领域通用语言(Ubiquitous Language)

    这个概念我也是第一次看到,作者在整个书中时不时要提一下这个概念.其实领域通用语言主要解决的是沟通问题,主要是业务人员和技术人员的沟通问题.不然开会的时候业务人员说自己的,技术人员说自己的,然后还谁都看谁不爽.

    其实,这个东西还是让技术人员妥协了,因为组成领域通用语言的以领域知识的概念为主.当然这也是必然的,除了一些技术性很强的项目(比如搞个云平台),多数项目的目的都是为了服务于业务,帮助公司业务部门为公司创造更多的利润.完成技术人员学习领域知识的过程作者称之为消化知识.

    模型驱动设计(Model-Driven Design)

    10年听说了TDD,其目的是让每一个功能有一个明确的完成界限.这回又听一个新东西MDD.模型驱动设计其实就是从模型开始出发,将领域通用语言中的概念映射到代码上,这样可以让代码直接体现出领域知识.让技术人员和业务人员的沟通在一件事情上,如果没有这层映射,他们的沟通必然存在转换.

    分层架构(Layered Archiecture)


    用户界面层(或表示层)

    应用层

    领域层(或模型层)

    基础设施层

    资源库(Repository)

    将所有对象存储和访问操作交给资源库来完成,而对于聚合一定控制只能从聚合根开始访问遍历.

    三、核心要点

    1. 将业务逻辑都内聚到业务领域层(domain层),将设计和开发的关注点聚焦到业务领域。

    2. 建立通用的业务领域语言,这是开发工程师和业务领域相关专家沟通的桥梁。

    3.战略上,将“有界域”(Bounded Context)解耦各个业务系统/组件,通过‘防腐层(Anticorruption layer)’确保自有业务领域不受外界污染,通过‘开放主机服务(Open Host Service)’向外界公开服务;

    1. 战术上,将业务对象建模为entity和value object。entity有唯一业务标识且在其生命周期中状态可变,value object与之相反;关联性强的entity和value object聚合成一个Aggregate,每个Aggregate有一个root entity,确保Aggregate内容业务规则和行为的一致性;

    业务行为尽量建模在entity/ value object 上,当业务行为无法建模到任何业务entity/value object时,可以使用领域服务(domain service),使用factory创建复杂的业务entity,使用repository实现实体的重建和持久化操作;领域相关的通知等可以通过domain event发布出去。

    四、图解

    User Interface —— 用户界面层。提供与用户/调用者交互的接口,可以是View,也可以是Restful api,还可以是二进制形式的tcp协议接口等。

    Application —— 应用服务层。组合domain和infrastructure,完成具体的业务逻辑。

    Domain —— 业务领域层。是ddd中的核心层,内聚所有的业务逻辑,保持领域的一致性。通常他可能会用到infrastructure层的公共组件。

    Infrastructure —— 基础设施层。提供公共服务组件,比如validation、登录态校验、trace日志记录等等。

    模式:

    包含核心概念和实现

    这张图囊括了DDD中的所有核心概念,上面部分是战术相关,下面部分是战略相关。不再赘述,建议读者直接查阅DDD原书,有详细介绍。值得反复细读。

    DDD适用场景

    1.业务逻辑复杂的系统;

    2.不太复杂的业务系统没有必要使用DDD,比如内容管理系统,只是对内容进行简单的增删改查;再比如渠道接入层系统,只是做请求的转发和翻译,没有核心业务逻辑,就没有必要使用DDD。

    3.有资深的业务领域专家支持,需要有一个很懂业务的人,在业务上提供指导性的意见;

    4.工程师都认可DDD设计思路,并积极主动地探讨和迭代,将业务模型日趋完善;

    5.项目工期压力小可以接受。

    DDD带来的好处:

    业务逻辑内聚到业务领域层,可以更好的保证业务规则的一致性;业务逻辑被合理的分散到不同的领域对象中,代码结构更加清晰,可读性,可维护性更高。

    对象职责更加单一,内聚度更高。

    系统高度模块化,代码重用度高,不会出现太多的重复逻辑。

    软件的可维护性和可扩展性增强,工程师可以聚焦在业务领域层,并致力于业务领域模型的迭代和维护,适应新业务的发展;

    软件的可测试性增强,领域层代码不需要借助用户接口层的入口便可以独立测试业务逻辑,通过repository的哑实现可以摆脱对数据持久层的依赖;

    软件工程师和业务领域专家(产品经理)使用同一种语言交流,沟通成本降低,提升效率;【这一点在现实中或多或少在使用,但是比较模糊,很多时候大家会用到‘领域’这个词语,但是并不会刻意地坐下来讨论各自领域应当包含哪些实体......这也是大家可以进步的地方。】

    业务领域层代码可以独立出来,共享到其他地方,可以轻松将服务产品化,云化;【这一点是yy出来的,可忽略。】

    DDD不好的地方:

    对工程师有较高的业务建模技能要求,期望他们能从复杂的业务上下文中识别出正确的业务模型,并将各个业务行为归入合适的entity/value object/domain service中;

    项目前期需要投入更多的时间进行业务建模,不能快速进入开发阶段;

    需要资深的业务领域专家参与进来,否则业务模型的频繁重构(重大改动)会带来额外成本;

    参考:
    领域驱动设计精要

    领域驱动设计(DDD:Domain-Driven Design)

    读<<领域驱动设计-软件核心复杂性应对之道>>有感

    相关文章

      网友评论

          本文标题:[DDD]领域驱动设计概要

          本文链接:https://www.haomeiwen.com/subject/kcipdhtx.html