1、项目架构

采用DDD分层架构和调用流程

项目现状

目前项目采用传统三层架构(MVC架构),三层架构就是一种严格分层模式,它把职责划分为界面展示、业务逻辑、数据访问三层,还有一个业务实体,前面三层都要依赖它,所以它并不构成一个层。这里有个问题,严格分层架构只能上层访问相邻的下层所以我们时常会遇到这个问题,action想直接从dao拿数据,但非要经过service,虽然可以提升安全性和使组织架构更严密,但却写了很多重复的代码(空壳调用)。

项目架构设计需求 2020项目架构_开发语言

 

后端分层:

  1. 表现层:controller
  2. 逻辑层:service
  3. 数据访问层:dao

三层架构缺点

分层太粗,模型层混乱

模型层逻辑太重,所有业务逻辑和代码组装都在模型层,导致模型层代码混乱,难以阅读

过于关注调用过程,业务边界模糊

MVC架构其实是面向过程编程,在功能设计时过于关注调用过程而忽视业务边界,长此以往会导致业务边界模糊代码职责不清等

代码复用率低

MVC架构其实是面向过程的代码堆砌,可复用率极低

祖传代码改动费劲

改一处代码可能会影响N处业务逻辑,原来的方法经历过几波开发人员后就会变成祖传代码,变得难以维护,最后只能重构项目,耗时耗力。

升级后

采用DDD分层架构,完整的DDD比较复杂,需要对业务非常了解,因此这里采用介于三层架构与DDD之间的架构设计

DDD架构特点:

  1. 数据、缓存等都视为基础层, 可以被所有层调用
  2. 抽离了领域层,负责核心业务逻辑处理,领域层调用外部依赖全部通过接口,以保证领域层的100%单测覆盖率
  3. 应用层聚合多个领域层的能力,只做功能的组合、转发,不负责具体业务逻辑

优点:相比于三层方式,更关注领域服务,即业务核心逻辑的划分、收敛。每个领域都可以看成是一个微服务,后期拆分微服务时更加容易。

缺点:分层复杂, 如果业务逻辑简单没有必要

项目架构设计需求 2020项目架构_java_02

与传统三层架构的对比

DDD四层架构也基于传统三层架构的,不同点有以下几方面:

  1. 关注点不一样:三层架构关注请求调用顺序;DDD架构关注领域服务。
  2. 横向划分方式不一样:三层架构主要关注纵向划分,对横向划分没有约定;DDD架构更关注纵向,即:多个领域层之间划分及交互方式。
  3. 对资源的定位不一样:三层架构把所有依赖的数据都放到数据访问层;DDD架构只将领域强关联的数据放到Repository中,其他比如API层缓存、文件等都当成基础服务来处理。
  4. DDD采用充血模型,对象自洽程度很高,表达能力很强,因此非常适合于复杂的企业业务逻辑的实现,以及可复用程度比较高,更符合面向对象设计思想。

调用过程:

项目架构设计需求 2020项目架构_开发语言_03

介于DDD与传统架构方案

采用DDD项目结构和调用方式但不使用领域对象创建方式

DDD领域对象使用充血模型

充血模型下领域对象作用此领域相关行为,包含此领域相关的业务逻辑,同时也包含对领域对象的持久化操作,对象属性中掺杂持久化仓库,设计比较复杂,需要对业务有较高的理解。

充血模型示例

@Entity
@Data
@Builder
@AllArgsConstructor
public class User implements UserService {

@Id
private String userId;
private String userName;
private String password;
private boolean isLock;

// 持久化仓库
@Transient
private UserRepository repo;

// 是否是持久化对象
@Transient
private boolean isRepository;

@PostLoad
public void per() {
isRepository = true;
}

public User() {
}

public User(UserRepository repo) {
this.repo = repo;
}

@Override
public void create(User user) {
repo.save(user);
}

@Override
public void edit(User user) {
if (!isRepository) {
throw new RuntimeException("用户不存在");
}

userName = user.userName;
repo.save(this);
// 发布领域事件 ...
}


@Override
public void lock() {
if (!isRepository) {
throw new RuntimeException("用户不存在");
}

isLock = true;
repo.save(this);
// 发布领域事件 ...
}

}

重构方案使用贫血模型

传统架构大都采用的方案,更符合大多数研发人员习惯。此种模型下领域对象的作用很简单,只有所有属性的get/set方式,以及少量简单的属性值转换,不包含任何业务逻辑,不关系对象持久化,只是用来做为数据对象的承载和传递的介质。

贫血模型示例:

@Entity
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {

@Id
private String userId;
private String userName;
private String password;
private boolean isLock;
}

真正的业务逻辑则由领域服务负责实现,此服务引入持久化仓库,在业务逻辑完成之后持久化到仓库中,并在此可以发布领域事件(Domain Event)

public interface UserService {

void create(User user);
void edit(User user);
void changePassword(String userId, String newPassword);
void lock(String userId);
void unlock(String userId);

}

@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserRepository repo;

@Override
public void edit(User user) {
User dbUser = repo.findById(user.getUserId()).get();
dbUser.setUserName(user.getUserName());
repo.save(dbUser);
// 发布领域事件 ...
}

@Override
public void lock(String userId) {
User dbUser = repo.findById(userId).get();
dbUser.setLock(true);
repo.save(dbUser);
// 发布领域事件 ...
}
 
// ... 省略完整代码
}

重构项目领域对象采用贫血模型,门槛低,更符合大多数开发人员习惯。

2、分层实体

每层使用独立的实体对象,各层间对象使用mapstruct进行转换。

mapstruct使用详解:MapStruct最详细的使用教程,别在用BeanUtils.copyProperties ()mapstruct教程

项目现状

一个对象贯穿整个业务流程。

优点:简单,高效

缺点:

1、修改或删除一个字段要考虑所有层级的影响范围,容易出错

2、各层间调用没有明显界限,层与层之间边界模糊,容易受到其它层业务入侵

3、如果未来需要拆分微服务,需要重新理清边界,工作量极大

升级后

每层使用独立的实体对象,各层间对象使用mapstruct进行转换。

项目架构设计需求 2020项目架构_项目架构设计需求_04

基础层

基础层的主要对象是 PO 对象。我们需要先建立 DO 和 PO 的映射关系。当 DO 数据需要持久化时,仓储服务会将 DO 转换为 PO 对象,完成数据库持久化操作。当 DO 数据需要初始化时,仓储服务从数据库获取数据形成 PO 对象,并将 PO 转换为 DO,完成数据初始化。大多数情况下 PO 和 DO 是一一对应的。但也有 DO 和 PO 多对多的情况,在 DO 和 PO 数据转换时,需要进行数据重组。

领域层

领域层的主要对象是 DO 对象。DO 是实体和值对象的数据和业务行为载体,承载着基础的核心业务逻辑。通过 DO 和 PO 转换,我们可以完成数据持久化和初始化。

应用层

应用层的主要对象是 DO 对象。如果需要调用其它微服务的应用服务,DO 会转换为 DTO,完成跨微服务的数据组装和传输。用户接口层先完成 DTO 到 DO 的转换,然后应用服务接收 DO 进行业务处理。如果 DTO 与 DO 是一对多的关系,这时就需要进行 DO 数据重组。

用户接口层

用户接口层会完成 DO 和 DTO 的互转,完成微服务与前端应用数据交互及转换。Facade 服务会对多个 DO 对象进行组装,转换为 DTO 对象,向前端应用完成数据转换和传输。

前端应用

前端应用主要是 VO 对象。展现层使用 VO 进行界面展示,通过用户接口层与应用层采用 DTO 对象进行数据交互。