人人都在跟风学微服务,却不知道DDD领域驱动设计?

2022-05-05 18:50:45 浏览数 (1)

微服务与DDD领域驱动设计模型

什么是DDD领域驱动设计

最先介绍领域驱动设计(domain-driven design)的是在程序员 Eric Evans 2004年出版的《领域驱动设计:复杂软件核心复杂应对之道》书籍中,领域驱动设计是领域概念的扩展和应用,并且将它应用在软件开发中。它的目标是将软件相关部分连接到不断发展的模型中,以此更容易创建复杂的应用。

有关DDD的实现案例可以参考下面这篇文章:

“【领域驱动设计在互联网业务开发中的实践】 https://tech.meituan.com/2017/12/22/ddd-in-practice.html ”

关于DDD你需要知道的

贫血模型

“贫血领域对象 贫血领域对象(Anemic Domain Object)是指仅用作数据载体,而没有行为和动作的领域对象。 ”

简单来说,就是只有Getter/Setter方法的实体。既然有贫血领域对象,那也就有充血领域对象。

“充血领域对象 实体除了Getter/Setter方法,还有描述实体行为和动作的方法 ”

我们举个例子,这里有一个订单实体Order

上面实体并没有描述实体行为的方法,所以该实体是贫血模型。

如果我们需要一个专门修改订单为发货的方法,可以这样写

如果用充血模型写是什么样子的呢?

我们增加了描述订单状态行为的方法buildDeliveryStatus()

订单发货的方法修改为

这里我们直接调用了order.buildDeliveryStatus(),来获取发货状态的行为,隐藏了具体行为是怎生成的。

其实按通常思路来说贫血模型没毛病,但是当业务逻辑复杂了,业务逻辑,状态会散落在大量的方法中,原本代码的意图会渐渐不明确。

“我认为使用充血模型开发人员不用在意行为的具体细节,只需要使用这个行为即可,符合面向对象封装的设计原则 ”

聚合根

Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。

实体

当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity)。

值对象

当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象(Value Object)。

聚合根

微服务为什么需要DDD领域驱动设计

《微服务架构与设计模式》在第二章服务的拆分策略中写道,我们在将单体服务拆分成微服务时,可以按照下面几种拆分方式:

  1. 按业务能力拆分
  2. 按子域模式拆分

本篇我们不讨论什么是微服务,如果不了解微服务的童鞋,可以看看这本书。

在大型软件开发中,让组织内所有团队都对全局单一的建模和术语定义达成一致时非常困难的,组织内有些团队可能针对不同的概念使用了相同的术语,有些团队可能针对同一个概念使用了不同的术语,DDD可以通过定义多个领域模型来避免这些问题,每个领域模型都有自己明确的范围。

在划分服务前,我们首先需要创建抽象的领域模型,每个服务都有自己的领域模型,抽象的领域模型有助于在服务划分阶段提供帮助,它定义了描述操作系统操作的行为的一些词语。

领域模型

上图为系统开发初期的领域模型示意图。

按子域模式拆分就是采用了DDD领域驱动设计的思想。领域驱动设计是构建复杂软件很有效的方法论。在领域驱动设计中,领域模型是核心,领域驱动设计有两个重要的概念:子域和限界上下文。

领域中的一部分就是子域,领域的边界就是限界上下文。

限界上下文

如上图,椭圆形虚线表示限界上下文,虚线包围的为子域。每个子域映射一个领域模型。

“DDD中子域和限界上下文的概念,能很好的跟微服务架构中的服务匹配,微服务架构中的自治化团队负责开发的概念跟DDD中每个领域模型都由一个独立团队负责开发的概念吻合。 ”

领域驱动设计的分层架构

传统的架构流行的是三层架构。

  • 表现层(Controller层):包含实现用户界面或外部API的代码
  • 业务逻辑层(Service层):包含因业务逻辑
  • 数据持久化层(Dao层):实现与数据库交互的逻辑

这种分层架构错误的表示了精心设计的应用程序的依赖关系。业务逻辑通常定义了数据访问方法的接口(增删改查逻辑)。数据持久化层则定义了实现数据库接口的Dao类。这种依赖关系与分层架构所描述的相反。

领域驱动模型中的分层架构

领域分层

  • User Interface-用户界面层:Controller、restful接口调用,或者web端UI界面、移动端UI界面、第三方服务等;
  • Application-应用层:对外为展现层提供各种应用功能,对内调用领域层(领域服务),应用层更像是实现某个特定场景的策略,或者流程上的编排等。其协同多个领域的服务,实现场景和业务的隔离;
  • Domain-领域层:负责表达业务概念,业务行为,业务状态以及业务规则,领域模型处于这一层,是业务软件的核心。其提供的是一系列原子服务,在这一层提供丰富的OPEN API,是实现组件化至关重要的一步;
  • Infrastructure-基础层:实现业务和技术的隔离层。一般包含:网络通讯、数据库持久化、异步消息服务、南向网关服务等。这一层在落地的时候,可以实现多种适配器adaptor 来兼容对内、对外、多云异构中间件环境。

对于这样分层的思考

在一个面向对象的程序中,用户界面、数据库以及其他支持性代码经常被直接写到业务对象中。附加的业务逻辑被嵌入到UI 组件和数据库脚本的行为中。之所以这样做的某些原因是这样可以很容易地让事情快速工作起来。

但是,当领域相关的代码被混入到其他层时,要阅读和思考它也变得极其困难。表面看上去是对UI 的修改,却变成了对业务逻辑的修改。对业务规则的变更可能需要谨慎跟踪用户界面层代码、数据库代码以及其他程序元素。实现粘连在了一起,模型驱动对象于是变得不再可行。也很难使用自动化测试。对于每个活动中涉及到的技术和逻辑,程序必须保持简单,否则就会变得很难理解。

所以我们需要将一个复杂的应用程序正确的切分成层,开发每一个层中的内聚设计,将领域模型相关的代码集中到自己的层中,把它从用户界面、应用和基础设施代码中隔离开来。

如果代码没有被清晰的的隔离到某层中,整个应用层序代码会显得很混乱,并且变得难以管理。在一处对代码进行简单的修改会对其他地方的代码造成未知隐患。领域层应该关心领域层的问题,它不应该涉及其他层的活动。

具体的领域模型分层代码实战

项目中包名可以参考上图。

下面来看看我的项目是如何引用DDD领域驱动设计模型的:

首先我创建了几个包:application、controller、domain、infrastructure。这里的controller包实际就是User interface层。

application

application里面主要是写一些业务方面的service

domain

domain里面主要是写一些与数据库打交道的方法(基础的增删改查)。

infrastructure

infrastructure主要是写一些公共类,比如配置类configure,常量类constant,枚举enum,工具类utils。供给其他层使用。

最后,对DDD感兴趣的童鞋推荐两本书

《领域驱动设计:软件核心复杂性应对之道》

《实现领域驱动设计》

0 人点赞