DDD-经典四层架构应用

荒凉一梦 提交于 2020-01-16 11:34:06

DDD分层与传统三层区别

根据DDD领域驱动设计原则,对应的软件架构也需要做出相应的调整。
我们常用的三层架构模型划分为表现层,业务逻辑层,数据访问层等,在DDD分层结构中既有联系又有区别,
个人认为主要有如下异同:

  • 在架构设计上,在DDD分层结构中将传统三层架构的业务逻辑层拆解为应用层和领域层
    其中Application划分为很薄的一层服务,非核心的逻辑放到此层去实现,核心的业务逻辑表现下沉到领域层去实现,凝练为更为精确的业务规则集合,通过领域对象去阐述说明。
    在这里插入图片描述

  • 在建模方式上,DDD分层的建模思维方式有别于传统三层
    传统三层通常是以数据库为起点进行数据库分析设计,而DDD则需要以业务领域模型为核心建模(即面向对象建模方式),更能体现对现实世界的抽象。
    在DDD分层凸显领域层的重要作用,领域层为系统的核心,包括所有的业务领域模型的抽象表达

  • 在职责划分上,基础设施层涵盖了2方面内容

    • 持久化功能,其中原三层架构的数据访问层下沉到基础设施层的持久化机制实现
    • 通用技术支持,一些公共通用技术支持也放到基础设施层去实现。

DDD分层详解

四层架构图

在这里插入图片描述
在该架构中,上层模块可以调用下层模块,反之不行。即

  • Interface ——> application | domain | infrastructure
  • application ——>domain | infrastructure
  • domain ——>infrastructure

分层作用

分层 英文 描述
表现层 User Interface 用户界面层,或者表现层,负责向用户显示解释用户命令
应用层 Application Layer 定义软件要完成的任务,并且指挥协调领域对象进行不同的操作。该层不包含业务领域知识。
领域层 Domain Layer 或称为模型层,系统的核心,负责表达业务概念,业务状态信息以及业务规则。即包含了该领域(问题域)所有复杂的业务知识抽象和规则定义。该层主要精力要放在领域对象分析上,可以从实体,值对象,聚合(聚合根),领域服务,领域事件,仓储,工厂等方面入手
基础设施层 Infrastructure Layer 主要有2方面内容,一是为领域模型提供持久化机制,当软件需要持久化能力时候才需要进行规划;一是对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现;

领域对象

根据战术设计,关注的领域对象主要包括有

类型 英文 描述
值对象 value object 无唯一标识的简单对象
实体 entity 充血的领域模型,有唯一标识
聚合(聚合根) aggregate 实体的聚合,拥有聚合根,可为某一个实体
领域服务 service 无法归类到某个具体领域模型的行为
领域事件 event 不常用
仓储 repository 持久化相关,与基础设施层关联
工厂 factory 负责复杂对象创建
模块 module 子模块引入,可以理解为子域划分

DDD编码实践

代码结构描述

eg.后端Java代码工程为例,
表现层在此代码结构中表现为api层,对外暴露接口的最上层

├─com.company.microservice
│    │ 
│    ├─apis   API接口层
│    │    ├─apiModel     视图模型,数据模型定义 vo/dto(大多數情況是一樣的)
│    │    ├─assembler    装配器,实现模型转换eg. apiModel<=> domainModel
│    │    └─controller   控制器,对外提供(Restful)接口
│    │ 
│    ├─application   应用层
│    │    ├─service  应用服务,非核心服务
│    │    ├─task     任务定义,协调领域模型 
│    │    └─***      others
│    │ 
│    ├─domain   领域层
│    │    ├─common       公共代码抽取,限于领域层有效 
│    │    ├─model        领域模型 
│    │    │    ├─xxxVO.java     值对象
│    │    │    ├─xxxEntity.java 实体类,充血的领域模型
│    │    │    └─xxxAgg.java    聚合类,通常表现为实体的聚合,需要有聚合根
│    │    ├─service      领域服务类,一些不能归属某个具体领域模型的行为
│    │    └─factory      工厂类,负责复杂领域对象创建,封装细节 
│    │ 
│    ├─infrastructure  基础设施层
│    │    ├─persistent   持久化机制
│    │    │    ├─po           持久化对象 
│    │    │    └─repository   仓储类,持久化接口&实现,可与ORM映射框架结合
│    │    ├─general      通用技术支持,向其他层输出通用服务
│    │    │    ├─config       配置类
│    │    │    ├─toolkit      工具类  
│    │    │    └─common       基础公共模块等
│    │ 
│    └─resources  
│        ├─statics  静态资源
│        ├─template 系统页面 
│        └─application.yml   全局配置文件

其中在上述目录结构中,Domain层中为对module进行划分,实际上默认该层只有一个模块,根据微服务划分可以进行增加模块来规范代码结构。

领域模型注入仓储类的问题

区别于传统的分层后,在domain中更多关注业务逻辑,考虑到要与spring框架集成,需要注意一个领域模型中注入仓储类的问题

在传统分层中,controller,service,repo均注册为spring管理的bean,
但是在domain层中,service一部分的业务逻辑划分到了具体的领域对象中去实现了,显然这些对象却不能注册为单例bean,
因此在此处不能沿用与原来分层结构中service层中通过@Autowired or @Resource等注入仓储接口,

关于这个问题,此处建议使用ApplicationContext实现

即通过一个工具类 ApplicationContextUtils 实现 ApplicationContextAware获取bean的方法,即 getBean()方法,
然后我们就可以在我们的领域模型中直接应用该工具类来获取Spring托管的singleton对象,即
xxxRepo=ApplicationContextUtils.getBean(“xxxRepository”)

@Component
public class ApplicationContextUtils implements ApplicationContextAware {

    public static ApplicationContext appctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtil.appctx=applicationContext;
    } 

    /**
     * @return ApplicationContext
     */
    public static ApplicationContext getApplicationContext() {
        return appctx;
    }

    /**
     * 获取对象
     *
     * @param name spring配置文件中配置的bean名或注解的名称
     * @return 一个以所给名字注册的bean的实例
     * @throws BeansException 抛出spring异常
     */ 
    public static <T> T getBean(String name) throws BeansException {
        return (T) appctx.getBean(name);
    }

    /**
     * 获取类型为requiredType的对象
     *
     * @param clazz 需要获取的bean的类型
     * @return 该类型的一个在ioc容器中的bean
     * @throws BeansException 抛出spring异常
     */
    public static <T> T getBean(Class<T> clazz) throws BeansException {
        return appctx.getBean(clazz);
    }

    /**
     * 如果ioc容器中包含一个与所给名称匹配的bean定义,则返回true否则返回false
     *
     * @param name ioc容器中注册的bean名称
     * @return 存在返回true否则返回false
     */
    public static boolean containsBean(String name) {
        return appctx.containsBean(name);
    }
}

考虑到代码结构,我们还可以封装一层仓储工厂,只用来获取相应的仓储Bean,简化代码结构。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!