很多开发者在做业务系统时,常常陷入代码耦合、需求迭代困难、单测无法落地的困境,本质上是没有选对合适的架构模式,或是对架构的核心设计理解不到位。分层、六边形、整洁架构是企业级Java开发中最核心的三种架构模式,三者一脉相承,从传统的水平分层到现代化的领域驱动设计适配,核心都是解决「关注点分离」与「依赖管理」这两个架构设计的本质问题。
一、架构设计的核心底层逻辑
无论哪种架构模式,都遵循四个核心设计原则,这是所有架构设计的根逻辑,也是判断架构是否合理的核心标准。
- 关注点分离:将系统按职责拆分为不同模块,每个模块只负责单一维度的事情,避免单模块承担过多职责导致的耦合。
- 依赖反转原则:高层模块不应该依赖低层模块,二者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。这是打破传统分层耦合的核心。
- 单向依赖规则:所有依赖必须有明确的方向,禁止循环依赖、反向依赖,确保系统的依赖链路清晰可控。
- 可测试性优先:好的架构必须支持单元测试,核心业务逻辑可以脱离数据库、框架、第三方接口等外部依赖独立测试。
二、分层架构——最经典的水平拆分架构
核心设计思想
分层架构是最早被广泛应用的企业级架构模式,核心思想是将系统按技术职责进行水平拆分,每一层只与相邻的下层交互,形成单向的依赖链路,实现职责的隔离与复用。
Java Web领域的标准四层分层架构,从上到下依次为:
- 表现层:负责接收客户端请求、参数校验、结果封装,不包含任何业务逻辑,对应Controller层。
- 业务逻辑层:负责核心业务规则的实现、事务控制,是系统的业务核心,对应Service层。
- 数据访问层:负责与数据库、缓存等存储介质交互,实现数据的增删改查,不包含业务逻辑,对应Mapper/DAO层。
- 数据模型层:负责系统内数据的载体,分为与数据库表对应的DO,以及各层之间传输的DTO。
核心设计规范
- 单向依赖原则:上层只能依赖下层,绝对禁止下层依赖上层。
- 职责单一原则:每一层只承担自身职责,禁止越权编写非本层的逻辑。
- 跨层通信规范:层与层之间的数据传输必须使用DTO,禁止将数据库DO直接传递到表现层。
- 异常处理规范:底层异常统一向上抛出,由表现层统一封装处理,禁止底层吞掉异常。
落地实现
Maven依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/>
</parent>
<groupId>com.jam.demo</groupId>
<artifactId>architecture-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>architecture-demo</name>
<description>Architecture Demo Project</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version>
<fastjson2.version>2.0.52</fastjson2.version>
<guava.version>33.2.0-jre</guava.version>
<lombok.version>1.18.34</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
MySQL表结构
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` varchar(64) NOT NULL COMMENT '用户名',
`password` varchar(128) NOT NULL COMMENT '密码',
`phone` varchar(11) DEFAULT NULL COMMENT '手机号',
`email` varchar(64) DEFAULT NULL COMMENT '邮箱',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除 0-未删除 1-已删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统用户表';
数据模型层代码
package com.jam.demo.layered.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 系统用户数据对象
* @author ken
*/
@Data
@TableName("sys_user")
@Schema(description = "系统用户数据对象")
public class SysUserDO {
@Schema(description = "主键ID")
@TableId(type = IdType.AUTO)
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "密码")
private String password;
@Schema(description = "手机号")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@Schema(description = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@Schema(description = "是否删除")
@TableLogic
private Integer deleted;
}
package com.jam.demo.layered.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 用户创建请求DTO
* @author ken
*/
@Data
@Schema(description = "用户创建请求DTO")
public class UserCreateDTO {
@NotBlank(message = "用户名不能为空")
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED)
private String username;
@NotBlank(message = "密码不能为空")
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
private String password;
@Schema(description = "手机号")
private String phone;
@Email(message = "邮箱格式不正确")
@Schema(description = "邮箱")
private String email;
}
package com.jam.demo.layered.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户响应DTO
* @author ken
*/
@Data
@Schema(description = "用户响应DTO")
public class UserRespDTO {
@Schema(description = "主键ID")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "手机号")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}
数据访问层代码
package com.jam.demo.layered.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.layered.entity.SysUserDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统用户Mapper接口
* @author ken
*/
@Mapper
public interface SysUserMapper extends BaseMapper<SysUserDO> {
}
业务逻辑层代码
package com.jam.demo.layered.service;
import com.jam.demo.layered.dto.UserCreateDTO;
import com.jam.demo.layered.dto.UserRespDTO;
import java.util.List;
/**
* 系统用户服务接口
* @author ken
*/
public interface SysUserService {
/**
* 创建用户
* @param createDTO 用户创建参数
* @return 用户ID
*/
Long createUser(UserCreateDTO createDTO);
/**
* 根据ID查询用户
* @param id 用户ID
* @return 用户信息
*/
UserRespDTO getUserById(Long id);
/**
* 查询所有用户
* @return 用户列表
*/
List<UserRespDTO> listAllUsers();
/**
* 根据ID删除用户
* @param id 用户ID
*/
void deleteUserById(Long id);
}
package com.jam.demo.layered.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.jam.demo.layered.dto.UserCreateDTO;
import com.jam.demo.layered.dto.UserRespDTO;
import com.jam.demo.layered.entity.SysUserDO;
import com.jam.demo.layered.mapper.SysUserMapper;
import com.jam.demo.layered.service.SysUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionTemplate;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* 系统用户服务实现类
* @author ken
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl implements SysUserService {
private final SysUserMapper sysUserMapper;
private final TransactionTemplate transactionTemplate;
@Override
public Long createUser(UserCreateDTO createDTO) {
if (!StringUtils.hasText(createDTO.getUsername())) {
throw new IllegalArgumentException("用户名不能为空");
}
LambdaQueryWrapper<SysUserDO> queryWrapper = new LambdaQueryWrapper<SysUserDO>()
.eq(SysUserDO::getUsername, createDTO.getUsername());
Long count = sysUserMapper.selectCount(queryWrapper);
if (count > 0) {
throw new IllegalArgumentException("用户名已存在");
}
SysUserDO userDO = new SysUserDO();
BeanUtils.copyProperties(createDTO, userDO);
return transactionTemplate.execute(status -> {
try {
sysUserMapper.insert(userDO);
log.info("创建用户成功,用户信息:{}", JSON.toJSONString(userDO));
return userDO.getId();
} catch (Exception e) {
status.setRollbackOnly();
log.error("创建用户失败,参数:{}", JSON.toJSONString(createDTO), e);
throw new RuntimeException("创建用户失败", e);
}
});
}
@Override
public UserRespDTO getUserById(Long id) {
if (ObjectUtils.isEmpty(id)) {
throw new IllegalArgumentException("用户ID不能为空");
}
SysUserDO userDO = sysUserMapper.selectById(id);
if (ObjectUtils.isEmpty(userDO)) {
throw new IllegalArgumentException("用户不存在");
}
UserRespDTO respDTO = new UserRespDTO();
BeanUtils.copyProperties(userDO, respDTO);
return respDTO;
}
@Override
public List<UserRespDTO> listAllUsers() {
List<SysUserDO> userDOList = sysUserMapper.selectList(null);
List<UserRespDTO> respList = Lists.newArrayListWithCapacity(userDOList.size());
for (SysUserDO userDO : userDOList) {
UserRespDTO respDTO = new UserRespDTO();
BeanUtils.copyProperties(userDO, respDTO);
respList.add(respDTO);
}
return respList;
}
@Override
public void deleteUserById(Long id) {
if (ObjectUtils.isEmpty(id)) {
throw new IllegalArgumentException("用户ID不能为空");
}
transactionTemplate.execute(status -> {
try {
int rows = sysUserMapper.deleteById(id);
if (rows == 0) {
throw new IllegalArgumentException("用户不存在");
}
log.info("删除用户成功,用户ID:{}", id);
return rows;
} catch (Exception e) {
status.setRollbackOnly();
log.error("删除用户失败,用户ID:{}", id, e);
throw new RuntimeException("删除用户失败", e);
}
});
}
}
表现层代码
package com.jam.demo.layered.controller;
import com.jam.demo.layered.dto.UserCreateDTO;
import com.jam.demo.layered.dto.UserRespDTO;
import com.jam.demo.layered.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 系统用户控制器
* @author ken
*/
@RestController
@RequestMapping("/layered/user")
@RequiredArgsConstructor
@Tag(name = "分层架构-用户管理", description = "分层架构用户管理接口")
public class SysUserController {
private final SysUserService sysUserService;
@PostMapping
@Operation(summary = "创建用户", description = "创建新的系统用户")
public ResponseEntity<Long> createUser(@Valid @RequestBody UserCreateDTO createDTO) {
Long userId = sysUserService.createUser(createDTO);
return ResponseEntity.ok(userId);
}
@GetMapping("/{id}")
@Operation(summary = "查询用户", description = "根据用户ID查询用户信息")
public ResponseEntity<UserRespDTO> getUserById(
@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
UserRespDTO user = sysUserService.getUserById(id);
return ResponseEntity.ok(user);
}
@GetMapping("/list")
@Operation(summary = "用户列表", description = "查询所有系统用户列表")
public ResponseEntity<List<UserRespDTO>> listAllUsers() {
List<UserRespDTO> userList = sysUserService.listAllUsers();
return ResponseEntity.ok(userList);
}
@DeleteMapping("/{id}")
@Operation(summary = "删除用户", description = "根据用户ID删除用户")
public ResponseEntity<Void> deleteUserById(
@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
sysUserService.deleteUserById(id);
return ResponseEntity.noContent().build();
}
}
优势与局限
- 优势:结构简单、上手门槛低,职责清晰符合常规开发习惯,代码复用性强,适合新人快速上手。
- 局限:业务层依赖数据访问层,更换存储介质需要修改业务代码,违反开闭原则;业务逻辑容易被稀释到各层,复杂场景下Service层会快速臃肿。
三、六边形架构——端口与适配器的内外分离架构
核心设计思想
六边形架构(又称端口与适配器架构)由Alistair Cockburn在2005年提出,核心思想是将系统的核心业务逻辑与外部依赖完全隔离,通过端口定义交互契约,由适配器实现外部交互,让核心业务不再依赖任何外部技术实现。
六边形架构的核心是「内外之分」而非「上下之分」,六边形内部是核心业务域,外部是所有驱动者与被驱动者,内外之间通过端口和适配器交互。核心概念分为两类:
- 端口:系统内部定义的交互契约,分为入站端口(定义系统对外提供的能力)和出站端口(定义系统对外部的依赖要求)。
- 适配器:端口的实现,负责格式与协议转换,分为主适配器(驱动系统执行用例,如Controller)和从适配器(被系统驱动,如数据库仓储实现)。
核心设计规范
- 核心业务域无外部依赖:核心业务代码不能出现任何外部框架、技术的类与注解。
- 端口契约优先:所有交互必须通过端口定义的契约,适配器只能实现端口接口,不能扩展业务逻辑。
- 严格执行依赖反转:出站端口由核心业务域定义,由从适配器实现,核心域只依赖接口不依赖实现。
- 业务逻辑唯一归属:所有业务规则必须放在核心业务域,适配器仅负责格式与协议转换。
落地实现
核心业务域-领域实体
package com.jam.demo.hexagonal.domain.entity;
import java.time.LocalDateTime;
/**
* 用户领域实体
* @author ken
*/
public class User {
private Long id;
private String username;
private String password;
private String phone;
private String email;
private LocalDateTime createTime;
private LocalDateTime updateTime;
public User() {}
public User(Long id, String username, String password, String phone, String email, LocalDateTime createTime, LocalDateTime updateTime) {
this.id = id;
this.username = username;
this.password = password;
this.phone = phone;
this.email = email;
this.createTime = createTime;
this.updateTime = updateTime;
}
/**
* 校验用户名是否合法
* @return 校验结果
*/
public boolean isUsernameValid() {
return username != null && username.length() >= 3 && username.length() <= 64;
}
/**
* 校验邮箱格式是否合法
* @return 校验结果
*/
public boolean isEmailValid() {
if (email == null || email.isEmpty()) {
return true;
}
return email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
public LocalDateTime getUpdateTime() { return updateTime; }
public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
}
核心业务域-入站端口
package com.jam.demo.hexagonal.domain.port.in;
import com.jam.demo.hexagonal.domain.entity.User;
import java.util.List;
/**
* 用户管理入站端口
* @author ken
*/
public interface UserUseCase {
/**
* 创建用户
* @param user 用户领域对象
* @return 创建后的用户ID
*/
Long createUser(User user);
/**
* 根据ID查询用户
* @param id 用户ID
* @return 用户领域对象
*/
User getUserById(Long id);
/**
* 查询所有用户
* @return 用户列表
*/
List<User> listAllUsers();
/**
* 根据ID删除用户
* @param id 用户ID
*/
void deleteUserById(Long id);
}
核心业务域-出站端口
package com.jam.demo.hexagonal.domain.port.out;
import com.jam.demo.hexagonal.domain.entity.User;
import java.util.List;
import java.util.Optional;
/**
* 用户仓储出站端口
* @author ken
*/
public interface UserRepository {
/**
* 保存用户
* @param user 用户领域对象
* @return 保存后的用户ID
*/
Long save(User user);
/**
* 根据ID查询用户
* @param id 用户ID
* @return 用户领域对象
*/
Optional<User> findById(Long id);
/**
* 根据用户名查询用户
* @param username 用户名
* @return 用户领域对象
*/
Optional<User> findByUsername(String username);
/**
* 查询所有用户
* @return 用户列表
*/
List<User> findAll();
/**
* 根据ID删除用户
* @param id 用户ID
* @return 影响行数
*/
int deleteById(Long id);
}
核心业务域-业务逻辑实现
package com.jam.demo.hexagonal.domain.service;
import com.jam.demo.hexagonal.domain.entity.User;
import com.jam.demo.hexagonal.domain.port.in.UserUseCase;
import com.jam.demo.hexagonal.domain.port.out.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.util.ObjectUtils;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
/**
* 用户领域服务实现
* @author ken
*/
@RequiredArgsConstructor
public class UserService implements UserUseCase {
private final UserRepository userRepository;
@Override
public Long createUser(User user) {
if (!user.isUsernameValid()) {
throw new IllegalArgumentException("用户名长度必须在3-64位之间");
}
if (!user.isEmailValid()) {
throw new IllegalArgumentException("邮箱格式不正确");
}
Optional<User> existUser = userRepository.findByUsername(user.getUsername());
if (existUser.isPresent()) {
throw new IllegalArgumentException("用户名已存在");
}
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
return userRepository.save(user);
}
@Override
public User getUserById(Long id) {
if (ObjectUtils.isEmpty(id)) {
throw new IllegalArgumentException("用户ID不能为空");
}
return userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("用户不存在"));
}
@Override
public List<User> listAllUsers() {
return userRepository.findAll();
}
@Override
public void deleteUserById(Long id) {
if (ObjectUtils.isEmpty(id)) {
throw new IllegalArgumentException("用户ID不能为空");
}
int rows = userRepository.deleteById(id);
if (rows == 0) {
throw new IllegalArgumentException("用户不存在");
}
}
}
适配器层-主适配器(Controller)
package com.jam.demo.hexagonal.adapter.in.web;
import com.jam.demo.hexagonal.domain.entity.User;
import com.jam.demo.hexagonal.domain.port.in.UserUseCase;
import com.jam.demo.hexagonal.dto.UserCreateDTO;
import com.jam.demo.hexagonal.dto.UserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户管理Web主适配器
* @author ken
*/
@RestController
@RequestMapping("/hexagonal/user")
@RequiredArgsConstructor
@Tag(name = "六边形架构-用户管理", description = "六边形架构用户管理接口")
public class UserController {
private final UserUseCase userUseCase;
@PostMapping
@Operation(summary = "创建用户", description = "创建新的系统用户")
public ResponseEntity<Long> createUser(@Valid @RequestBody UserCreateDTO createDTO) {
User user = new User();
BeanUtils.copyProperties(createDTO, user);
Long userId = userUseCase.createUser(user);
return ResponseEntity.ok(userId);
}
@GetMapping("/{id}")
@Operation(summary = "查询用户", description = "根据用户ID查询用户信息")
public ResponseEntity<UserRespDTO> getUserById(
@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
User user = userUseCase.getUserById(id);
UserRespDTO respDTO = new UserRespDTO();
BeanUtils.copyProperties(user, respDTO);
return ResponseEntity.ok(respDTO);
}
@GetMapping("/list")
@Operation(summary = "用户列表", description = "查询所有系统用户列表")
public ResponseEntity<List<UserRespDTO>> listAllUsers() {
List<User> userList = userUseCase.listAllUsers();
List<UserRespDTO> respList = userList.stream().map(user -> {
UserRespDTO respDTO = new UserRespDTO();
BeanUtils.copyProperties(user, respDTO);
return respDTO;
}).collect(Collectors.toList());
return ResponseEntity.ok(respList);
}
@DeleteMapping("/{id}")
@Operation(summary = "删除用户", description = "根据用户ID删除用户")
public ResponseEntity<Void> deleteUserById(
@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
userUseCase.deleteUserById(id);
return ResponseEntity.noContent().build();
}
}
适配器层-传输对象
package com.jam.demo.hexagonal.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 用户创建请求DTO
* @author ken
*/
@Data
@Schema(description = "用户创建请求DTO")
public class UserCreateDTO {
@NotBlank(message = "用户名不能为空")
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED)
private String username;
@NotBlank(message = "密码不能为空")
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
private String password;
@Schema(description = "手机号")
private String phone;
@Email(message = "邮箱格式不正确")
@Schema(description = "邮箱")
private String email;
}
package com.jam.demo.hexagonal.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户响应DTO
* @author ken
*/
@Data
@Schema(description = "用户响应DTO")
public class UserRespDTO {
@Schema(description = "主键ID")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "手机号")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}
适配器层-从适配器(MySQL仓储实现)
package com.jam.demo.hexagonal.adapter.out.persistence.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 系统用户数据对象
* @author ken
*/
@Data
@TableName("sys_user")
public class SysUserDO {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String phone;
private String email;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
private Integer deleted;
}
package com.jam.demo.hexagonal.adapter.out.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.hexagonal.adapter.out.persistence.entity.SysUserDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统用户Mapper接口
* @author ken
*/
@Mapper
public interface SysUserMapper extends BaseMapper<SysUserDO> {
}
package com.jam.demo.hexagonal.adapter.out.persistence;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.jam.demo.hexagonal.adapter.out.persistence.entity.SysUserDO;
import com.jam.demo.hexagonal.adapter.out.persistence.mapper.SysUserMapper;
import com.jam.demo.hexagonal.domain.entity.User;
import com.jam.demo.hexagonal.domain.port.out.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionTemplate;
import java.util.List;
import java.util.Optional;
/**
* MySQL用户仓储从适配器实现
* @author ken
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepository {
private final SysUserMapper sysUserMapper;
private final TransactionTemplate transactionTemplate;
@Override
public Long save(User user) {
SysUserDO userDO = new SysUserDO();
BeanUtils.copyProperties(user, userDO);
return transactionTemplate.execute(status -> {
try {
sysUserMapper.insert(userDO);
log.info("保存用户成功,用户信息:{}", JSON.toJSONString(userDO));
return userDO.getId();
} catch (Exception e) {
status.setRollbackOnly();
log.error("保存用户失败,参数:{}", JSON.toJSONString(user), e);
throw new RuntimeException("保存用户失败", e);
}
});
}
@Override
public Optional<User> findById(Long id) {
SysUserDO userDO = sysUserMapper.selectById(id);
if (userDO == null) {
return Optional.empty();
}
User user = new User();
BeanUtils.copyProperties(userDO, user);
return Optional.of(user);
}
@Override
public Optional<User> findByUsername(String username) {
LambdaQueryWrapper<SysUserDO> queryWrapper = new LambdaQueryWrapper<SysUserDO>()
.eq(SysUserDO::getUsername, username);
SysUserDO userDO = sysUserMapper.selectOne(queryWrapper);
if (userDO == null) {
return Optional.empty();
}
User user = new User();
BeanUtils.copyProperties(userDO, user);
return Optional.of(user);
}
@Override
public List<User> findAll() {
List<SysUserDO> userDOList = sysUserMapper.selectList(null);
List<User> userList = Lists.newArrayListWithCapacity(userDOList.size());
for (SysUserDO userDO : userDOList) {
User user = new User();
BeanUtils.copyProperties(userDO, user);
userList.add(user);
}
return userList;
}
@Override
public int deleteById(Long id) {
return transactionTemplate.execute(status -> {
try {
int rows = sysUserMapper.deleteById(id);
log.info("删除用户成功,用户ID:{}", id);
return rows;
} catch (Exception e) {
status.setRollbackOnly();
log.error("删除用户失败,用户ID:{}", id, e);
throw new RuntimeException("删除用户失败", e);
}
});
}
}
Spring配置类
package com.jam.demo.hexagonal.config;
import com.jam.demo.hexagonal.domain.port.in.UserUseCase;
import com.jam.demo.hexagonal.domain.port.out.UserRepository;
import com.jam.demo.hexagonal.domain.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 六边形架构Bean配置类
* @author ken
*/
@Configuration
public class HexagonalBeanConfig {
@Bean
public UserUseCase userUseCase(UserRepository userRepository) {
return new UserService(userRepository);
}
}
优势与局限
- 优势:核心业务与外部技术完全解耦,更换外部依赖仅需替换适配器,符合开闭原则;可测试性极强,核心业务可脱离外部依赖测试;支持多端接入,适配性强。
- 局限:上手门槛高于分层架构,需要理解端口与适配器的设计理念;简单CRUD场景下代码量偏多,对团队规范要求较高。
四、整洁架构——严格依赖规则的圈层架构
核心设计思想
整洁架构由Robert C. Martin在2012年提出,在六边形架构的基础上进一步细化了系统内部圈层,提出了绝对依赖规则:圈层之间的依赖必须从外向内,内层代码绝对不能知道外层的任何信息,包括类名、方法名、注解、常量等。
整洁架构通过圈层划分,将业务核心与技术实现彻底隔离,确保业务核心是系统最稳定的部分,而易变化的技术实现放在最外层,实现高内聚、低耦合的设计目标。从内到外分为四个核心圈层:
- 实体层:也叫企业业务规则层,包含核心业务实体、值对象、领域服务,封装最核心的业务规则,是系统最稳定的部分。
- 用例层:也叫应用业务规则层,包含系统用例实现,编排业务流程,实现应用级业务规则,仅依赖实体层。
- 接口适配器层:负责内外数据格式的转换,对应六边形架构的适配器层,包含Controller、仓储实现、网关实现等。
- 框架与驱动层:最外层,包含所有框架、工具、数据库、第三方接口等技术实现,代码变化最频繁,不包含任何业务逻辑。
核心设计规范
- 绝对依赖规则:任何代码都不能依赖外层圈层,内层绝对不能引用外层的任何类、方法、注解。
- 业务规则唯一归属:企业级核心业务规则仅能放在实体层,应用级业务流程仅能放在用例层,外层不能包含任何业务规则。
- 数据格式隔离:每个圈层使用独立的数据模型,禁止跨圈层共享数据模型,层间必须通过适配器进行数据转换。
- 异常处理规则:内层抛出的业务异常由外层统一捕获处理,内层不能处理外层异常,也不能感知外层异常类型。
落地实现
实体层-值对象
package com.jam.demo.clean.entity;
/**
* 用户ID值对象
* @author ken
*/
public record UserId(Long value) {
public UserId {
if (value == null || value <= 0) {
throw new IllegalArgumentException("用户ID必须大于0");
}
}
}
package com.jam.demo.clean.entity;
/**
* 用户名值对象
* @author ken
*/
public record Username(String value) {
public Username {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("用户名不能为空");
}
if (value.length() < 3 || value.length() > 64) {
throw new IllegalArgumentException("用户名长度必须在3-64位之间");
}
}
}
package com.jam.demo.clean.entity;
/**
* 密码值对象
* @author ken
*/
public record Password(String value) {
public Password {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("密码不能为空");
}
if (value.length() < 6) {
throw new IllegalArgumentException("密码长度不能少于6位");
}
}
}
package com.jam.demo.clean.entity;
/**
* 手机号值对象
* @author ken
*/
public record Phone(String value) {
public Phone {
if (value == null || value.isBlank()) {
return;
}
if (!value.matches("^1[3-9]\\d{9}$")) {
throw new IllegalArgumentException("手机号格式不正确");
}
}
}
package com.jam.demo.clean.entity;
/**
* 邮箱值对象
* @author ken
*/
public record Email(String value) {
public Email {
if (value == null || value.isBlank()) {
return;
}
if (!value.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")) {
throw new IllegalArgumentException("邮箱格式不正确");
}
}
}
实体层-领域实体
package com.jam.demo.clean.entity;
import java.time.LocalDateTime;
/**
* 用户领域实体
* @author ken
*/
public class User {
private final UserId id;
private final Username username;
private final Password password;
private final Phone phone;
private final Email email;
private final LocalDateTime createTime;
private final LocalDateTime updateTime;
public User(UserId id, Username username, Password password, Phone phone, Email email, LocalDateTime createTime, LocalDateTime updateTime) {
this.id = id;
this.username = username;
this.password = password;
this.phone = phone;
this.email = email;
this.createTime = createTime;
this.updateTime = updateTime;
}
/**
* 创建新用户
* @param username 用户名
* @param password 密码
* @param phone 手机号
* @param email 邮箱
* @return 新用户实体
*/
public static User create(Username username, Password password, Phone phone, Email email) {
LocalDateTime now = LocalDateTime.now();
return new User(null, username, password, phone, email, now, now);
}
public UserId getId() { return id; }
public Username getUsername() { return username; }
public Password getPassword() { return password; }
public Phone getPhone() { return phone; }
public Email getEmail() { return email; }
public LocalDateTime getCreateTime() { return createTime; }
public LocalDateTime getUpdateTime() { return updateTime; }
}
实体层-仓储接口
package com.jam.demo.clean.entity;
import java.util.List;
import java.util.Optional;
/**
* 用户仓储接口
* @author ken
*/
public interface UserRepository {
/**
* 保存用户
* @param user 用户实体
* @return 保存后的用户ID
*/
UserId save(User user);
/**
* 根据ID查询用户
* @param id 用户ID
* @return 用户实体
*/
Optional<User> findById(UserId id);
/**
* 根据用户名查询用户
* @param username 用户名
* @return 用户实体
*/
Optional<User> findByUsername(Username username);
/**
* 查询所有用户
* @return 用户列表
*/
List<User> findAll();
/**
* 根据ID删除用户
* @param id 用户ID
*/
void deleteById(UserId id);
}
用例层-用例接口
package com.jam.demo.clean.usecase;
import com.jam.demo.clean.usecase.command.UserCreateCommand;
import com.jam.demo.clean.usecase.dto.UserDTO;
import java.util.List;
/**
* 用户管理用例接口
* @author ken
*/
public interface UserUseCase {
/**
* 创建用户
* @param command 创建用户命令
* @return 用户ID
*/
Long createUser(UserCreateCommand command);
/**
* 根据ID查询用户
* @param id 用户ID
* @return 用户DTO
*/
UserDTO getUserById(Long id);
/**
* 查询所有用户
* @return 用户DTO列表
*/
List<UserDTO> listAllUsers();
/**
* 根据ID删除用户
* @param id 用户ID
*/
void deleteUserById(Long id);
}
用例层-命令与DTO
package com.jam.demo.clean.usecase.command;
/**
* 创建用户命令
* @author ken
*/
public record UserCreateCommand(String username, String password, String phone, String email) {
}
package com.jam.demo.clean.usecase.dto;
import java.time.LocalDateTime;
/**
* 用户DTO
* @author ken
*/
public record UserDTO(Long id, String username, String phone, String email, LocalDateTime createTime) {
}
用例层-用例实现
package com.jam.demo.clean.usecase.impl;
import com.jam.demo.clean.entity.*;
import com.jam.demo.clean.usecase.UserUseCase;
import com.jam.demo.clean.usecase.command.UserCreateCommand;
import com.jam.demo.clean.usecase.dto.UserDTO;
import lombok.RequiredArgsConstructor;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户管理用例实现
* @author ken
*/
@RequiredArgsConstructor
public class UserUseCaseImpl implements UserUseCase {
private final UserRepository userRepository;
@Override
public Long createUser(UserCreateCommand command) {
Username username = new Username(command.username());
Password password = new Password(command.password());
Phone phone = new Phone(command.phone());
Email email = new Email(command.email());
if (userRepository.findByUsername(username).isPresent()) {
throw new IllegalArgumentException("用户名已存在");
}
User user = User.create(username, password, phone, email);
UserId userId = userRepository.save(user);
return userId.value();
}
@Override
public UserDTO getUserById(Long id) {
UserId userId = new UserId(id);
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("用户不存在"));
return new UserDTO(
user.getId().value(),
user.getUsername().value(),
user.getPhone() != null ? user.getPhone().value() : null,
user.getEmail() != null ? user.getEmail().value() : null,
user.getCreateTime()
);
}
@Override
public List<UserDTO> listAllUsers() {
List<User> userList = userRepository.findAll();
return userList.stream().map(user -> new UserDTO(
user.getId().value(),
user.getUsername().value(),
user.getPhone() != null ? user.getPhone().value() : null,
user.getEmail() != null ? user.getEmail().value() : null,
user.getCreateTime()
)).collect(Collectors.toList());
}
@Override
public void deleteUserById(Long id) {
UserId userId = new UserId(id);
userRepository.deleteById(userId);
}
}
接口适配器层-主适配器(Controller)
package com.jam.demo.clean.adapter.in.web;
import com.jam.demo.clean.adapter.in.web.request.UserCreateRequest;
import com.jam.demo.clean.adapter.in.web.response.UserResponse;
import com.jam.demo.clean.usecase.UserUseCase;
import com.jam.demo.clean.usecase.command.UserCreateCommand;
import com.jam.demo.clean.usecase.dto.UserDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户管理Web控制器
* @author ken
*/
@RestController
@RequestMapping("/clean/user")
@RequiredArgsConstructor
@Tag(name = "整洁架构-用户管理", description = "整洁架构用户管理接口")
public class UserController {
private final UserUseCase userUseCase;
@PostMapping
@Operation(summary = "创建用户", description = "创建新的系统用户")
public ResponseEntity<Long> createUser(@Valid @RequestBody UserCreateRequest request) {
UserCreateCommand command = new UserCreateCommand(
request.getUsername(),
request.getPassword(),
request.getPhone(),
request.getEmail()
);
Long userId = userUseCase.createUser(command);
return ResponseEntity.ok(userId);
}
@GetMapping("/{id}")
@Operation(summary = "查询用户", description = "根据用户ID查询用户信息")
public ResponseEntity<UserResponse> getUserById(
@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
UserDTO userDTO = userUseCase.getUserById(id);
UserResponse response = new UserResponse(
userDTO.id(),
userDTO.username(),
userDTO.phone(),
userDTO.email(),
userDTO.createTime()
);
return ResponseEntity.ok(response);
}
@GetMapping("/list")
@Operation(summary = "用户列表", description = "查询所有系统用户列表")
public ResponseEntity<List<UserResponse>> listAllUsers() {
List<UserDTO> userDTOList = userUseCase.listAllUsers();
List<UserResponse> responseList = userDTOList.stream().map(dto -> new UserResponse(
dto.id(),
dto.username(),
dto.phone(),
dto.email(),
dto.createTime()
)).collect(Collectors.toList());
return ResponseEntity.ok(responseList);
}
@DeleteMapping("/{id}")
@Operation(summary = "删除用户", description = "根据用户ID删除用户")
public ResponseEntity<Void> deleteUserById(
@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
userUseCase.deleteUserById(id);
return ResponseEntity.noContent().build();
}
}
接口适配器层-请求与响应对象
package com.jam.demo.clean.adapter.in.web.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 用户创建请求
* @author ken
*/
@Data
@Schema(description = "用户创建请求")
public class UserCreateRequest {
@NotBlank(message = "用户名不能为空")
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED)
private String username;
@NotBlank(message = "密码不能为空")
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
private String password;
@Schema(description = "手机号")
private String phone;
@Email(message = "邮箱格式不正确")
@Schema(description = "邮箱")
private String email;
}
package com.jam.demo.clean.adapter.in.web.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户响应
* @author ken
*/
@Data
@AllArgsConstructor
@Schema(description = "用户响应")
public class UserResponse {
@Schema(description = "主键ID")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "手机号")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}
接口适配器层-从适配器(MySQL仓储实现)
package com.jam.demo.clean.adapter.out.persistence.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 系统用户数据对象
* @author ken
*/
@Data
@TableName("sys_user")
public class SysUserDO {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String phone;
private String email;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
private Integer deleted;
}
package com.jam.demo.clean.adapter.out.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.clean.adapter.out.persistence.entity.SysUserDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统用户Mapper接口
* @author ken
*/
@Mapper
public interface SysUserMapper extends BaseMapper<SysUserDO> {
}
package com.jam.demo.clean.adapter.out.persistence;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.jam.demo.clean.adapter.out.persistence.entity.SysUserDO;
import com.jam.demo.clean.adapter.out.persistence.mapper.SysUserMapper;
import com.jam.demo.clean.entity.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.TransactionTemplate;
import java.util.List;
import java.util.Optional;
/**
* MySQL用户仓储实现
* @author ken
*/
@Slf4j
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepository {
private final SysUserMapper sysUserMapper;
private final TransactionTemplate transactionTemplate;
@Override
public UserId save(User user) {
SysUserDO userDO = new SysUserDO();
userDO.setUsername(user.getUsername().value());
userDO.setPassword(user.getPassword().value());
userDO.setPhone(user.getPhone() != null ? user.getPhone().value() : null);
userDO.setEmail(user.getEmail() != null ? user.getEmail().value() : null);
userDO.setCreateTime(user.getCreateTime());
userDO.setUpdateTime(user.getUpdateTime());
return transactionTemplate.execute(status -> {
try {
sysUserMapper.insert(userDO);
log.info("保存用户成功,用户信息:{}", JSON.toJSONString(userDO));
return new UserId(userDO.getId());
} catch (Exception e) {
status.setRollbackOnly();
log.error("保存用户失败,参数:{}", JSON.toJSONString(user), e);
throw new RuntimeException("保存用户失败", e);
}
});
}
@Override
public Optional<User> findById(UserId id) {
SysUserDO userDO = sysUserMapper.selectById(id.value());
if (userDO == null) {
return Optional.empty();
}
User user = new User(
new UserId(userDO.getId()),
new Username(userDO.getUsername()),
new Password(userDO.getPassword()),
userDO.getPhone() != null ? new Phone(userDO.getPhone()) : null,
userDO.getEmail() != null ? new Email(userDO.getEmail()) : null,
userDO.getCreateTime(),
userDO.getUpdateTime()
);
return Optional.of(user);
}
@Override
public Optional<User> findByUsername(Username username) {
LambdaQueryWrapper<SysUserDO> queryWrapper = new LambdaQueryWrapper<SysUserDO>()
.eq(SysUserDO::getUsername, username.value());
SysUserDO userDO = sysUserMapper.selectOne(queryWrapper);
if (userDO == null) {
return Optional.empty();
}
User user = new User(
new UserId(userDO.getId()),
new Username(userDO.getUsername()),
new Password(userDO.getPassword()),
userDO.getPhone() != null ? new Phone(userDO.getPhone()) : null,
userDO.getEmail() != null ? new Email(userDO.getEmail()) : null,
userDO.getCreateTime(),
userDO.getUpdateTime()
);
return Optional.of(user);
}
@Override
public List<User> findAll() {
List<SysUserDO> userDOList = sysUserMapper.selectList(null);
List<User> userList = Lists.newArrayListWithCapacity(userDOList.size());
for (SysUserDO userDO : userDOList) {
User user = new User(
new UserId(userDO.getId()),
new Username(userDO.getUsername()),
new Password(userDO.getPassword()),
userDO.getPhone() != null ? new Phone(userDO.getPhone()) : null,
userDO.getEmail() != null ? new Email(userDO.getEmail()) : null,
userDO.getCreateTime(),
userDO.getUpdateTime()
);
userList.add(user);
}
return userList;
}
@Override
public void deleteById(UserId id) {
transactionTemplate.execute(status -> {
try {
int rows = sysUserMapper.deleteById(id.value());
if (rows == 0) {
throw new IllegalArgumentException("用户不存在");
}
log.info("删除用户成功,用户ID:{}", id.value());
return rows;
} catch (Exception e) {
status.setRollbackOnly();
log.error("删除用户失败,用户ID:{}", id.value(), e);
throw new RuntimeException("删除用户失败", e);
}
});
}
}
Spring配置类
package com.jam.demo.clean.config;
import com.jam.demo.clean.adapter.out.persistence.UserRepositoryImpl;
import com.jam.demo.clean.adapter.out.persistence.mapper.SysUserMapper;
import com.jam.demo.clean.entity.UserRepository;
import com.jam.demo.clean.usecase.UserUseCase;
import com.jam.demo.clean.usecase.impl.UserUseCaseImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionTemplate;
/**
* 整洁架构Bean配置类
* @author ken
*/
@Configuration
public class CleanBeanConfig {
@Bean
public UserRepository userRepository(SysUserMapper sysUserMapper, TransactionTemplate transactionTemplate) {
return new UserRepositoryImpl(sysUserMapper, transactionTemplate);
}
@Bean
public UserUseCase userUseCase(UserRepository userRepository) {
return new UserUseCaseImpl(userRepository);
}
}
优势与局限
- 优势:绝对的依赖隔离,核心业务完全不依赖外部技术,框架升级、技术替换不会影响核心业务;可测试性达到极致,内层代码可纯Java单元测试;业务规则高度内聚,团队分工明确,完全符合SOLID原则。
- 局限:上手门槛最高,对开发者的设计能力要求极高;代码量最大,简单场景下冗余度高;对团队规范要求极严,一旦违反依赖规则,架构优势将完全丧失。
五、三大架构的核心区别与选型指南
三者是一脉相承的演进关系:分层架构是基础,六边形架构解决了分层架构的依赖倒置问题,整洁架构在六边形架构的基础上进一步强化了依赖规则与圈层隔离。
| 对比维度 | 分层架构 | 六边形架构 | 整洁架构 |
| 核心思想 | 水平拆分,按技术职责分层 | 内外分离,端口与适配器隔离 | 圈层拆分,绝对依赖规则 |
| 依赖方向 | 上层依赖下层,业务层依赖数据访问层 | 所有依赖指向核心域,核心域不依赖外部 | 依赖必须从外向内,内层绝对不依赖外层 |
| 业务逻辑归属 | Service层,容易被稀释 | 核心业务域,严格隔离 | 实体层+用例层,绝对内聚 |
| 可测试性 | 弱,依赖数据库等外部资源 | 强,核心业务可脱离外部依赖测试 | 极强,内层代码可纯Java单元测试 |
| 上手门槛 | 低,符合常规开发习惯 | 中,需要理解端口与适配器 | 高,需要理解依赖规则与领域设计 |
| 代码量 | 少 | 中 | 多 |
| 适用场景 | 简单CRUD系统,低复杂度业务 | 中等复杂度业务,需频繁替换外部依赖 | 高复杂度企业级系统,多团队协作 |
选型核心原则
- 架构复杂度必须与业务复杂度匹配,没有最好的架构,只有最合适的架构。
- 简单CRUD管理系统、团队成员水平参差不齐,优先选择分层架构,足够支撑业务且上手成本低。
- 业务规则相对复杂、需要对接多种外部系统、多端接入的场景,优先选择六边形架构,平衡复杂度与可维护性。
- 企业级核心业务系统、业务规则复杂、生命周期长、多团队协作的场景,优先选择整洁架构,保障系统的长期可维护性。
六、架构落地的核心避坑指南
分层架构常见坑
- 跨层调用:Controller直接调用Mapper,跳过Service层,导致业务逻辑分散无法统一管控。
- 反向依赖:Service调用Controller方法、Mapper调用Service方法,导致循环依赖,架构崩溃。
- 职责越界:Controller或Mapper中编写业务逻辑,导致Service层空壳化,业务逻辑分散。
- 模型混用:将数据库DO直接传到前端,导致表结构泄露,层间耦合严重。
六边形架构常见坑
- 业务逻辑泄露:将业务逻辑写到适配器中,导致核心域空壳化,架构失效。
- 端口定义混乱:入站与出站端口不分,单个端口定义过多方法,违反单一职责原则。
- 核心域依赖外部:核心域引入外部框架的类与注解,破坏了内外隔离的核心设计。
- 适配器职责越界:适配器中加入业务判断,而非仅做格式与协议转换。
整洁架构常见坑
- 违反依赖规则:内层引用外层的类、方法、注解,破坏绝对依赖规则,架构优势荡然无存。
- 圈层职责混淆:应用级业务流程写入实体层,核心业务规则写入用例层,导致业务逻辑分散。
- 数据模型混用:跨圈层共享数据模型,导致层间耦合严重。
- 过度设计:简单CRUD系统强行使用整洁架构,导致代码冗余,开发效率大幅降低。
总结
架构设计的本质,是解决业务复杂度带来的代码耦合、迭代困难、维护成本高的问题。分层、六边形、整洁架构三者并非对立,而是一脉相承的演进关系,核心都围绕「关注点分离」与「依赖反转」这两个最本质的设计原则。
对于开发者而言,无需盲目追求高大上的架构,而是要根据业务复杂度、团队水平、系统生命周期选择最合适的架构。只有真正理解架构的底层逻辑,才能写出高内聚、低耦合、可维护、可扩展的代码,从根本上避免陷入屎山代码的困境。