很多开发者在做业务系统时,常常陷入代码耦合、需求迭代困难、单测无法落地的困境,本质上是没有选对合适的架构模式,或是对架构的核心设计理解不到位。分层、六边形、整洁架构是企业级Java开发中最核心的三种架构模式,三者一脉相承,从传统的水平分层到现代化的领域驱动设计适配,核心都是解决「关注点分离」与「依赖管理」这两个架构设计的本质问题。

一、架构设计的核心底层逻辑

无论哪种架构模式,都遵循四个核心设计原则,这是所有架构设计的根逻辑,也是判断架构是否合理的核心标准。

  1. 关注点分离:将系统按职责拆分为不同模块,每个模块只负责单一维度的事情,避免单模块承担过多职责导致的耦合。

  2. 依赖反转原则:高层模块不应该依赖低层模块,二者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。这是打破传统分层耦合的核心。

  3. 单向依赖规则:所有依赖必须有明确的方向,禁止循环依赖、反向依赖,确保系统的依赖链路清晰可控。

  4. 可测试性优先:好的架构必须支持单元测试,核心业务逻辑可以脱离数据库、框架、第三方接口等外部依赖独立测试。

二、分层架构——最经典的水平拆分架构

核心设计思想

分层架构是最早被广泛应用的企业级架构模式,核心思想是将系统按技术职责进行水平拆分,每一层只与相邻的下层交互,形成单向的依赖链路,实现职责的隔离与复用。

Java Web领域的标准四层分层架构,从上到下依次为:

  1. 表现层:负责接收客户端请求、参数校验、结果封装,不包含任何业务逻辑,对应Controller层。

  2. 业务逻辑层:负责核心业务规则的实现、事务控制,是系统的业务核心,对应Service层。

  3. 数据访问层:负责与数据库、缓存等存储介质交互,实现数据的增删改查,不包含业务逻辑,对应Mapper/DAO层。

  4. 数据模型层:负责系统内数据的载体,分为与数据库表对应的DO,以及各层之间传输的DTO。

核心设计规范

  1. 单向依赖原则:上层只能依赖下层,绝对禁止下层依赖上层。

  2. 职责单一原则:每一层只承担自身职责,禁止越权编写非本层的逻辑。

  3. 跨层通信规范:层与层之间的数据传输必须使用DTO,禁止将数据库DO直接传递到表现层。

  4. 异常处理规范:底层异常统一向上抛出,由表现层统一封装处理,禁止底层吞掉异常。

落地实现

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年提出,核心思想是将系统的核心业务逻辑与外部依赖完全隔离,通过端口定义交互契约,由适配器实现外部交互,让核心业务不再依赖任何外部技术实现

六边形架构的核心是「内外之分」而非「上下之分」,六边形内部是核心业务域,外部是所有驱动者与被驱动者,内外之间通过端口和适配器交互。核心概念分为两类:

  1. 端口:系统内部定义的交互契约,分为入站端口(定义系统对外提供的能力)和出站端口(定义系统对外部的依赖要求)。

  2. 适配器:端口的实现,负责格式与协议转换,分为主适配器(驱动系统执行用例,如Controller)和从适配器(被系统驱动,如数据库仓储实现)。

核心设计规范

  1. 核心业务域无外部依赖:核心业务代码不能出现任何外部框架、技术的类与注解。

  2. 端口契约优先:所有交互必须通过端口定义的契约,适配器只能实现端口接口,不能扩展业务逻辑。

  3. 严格执行依赖反转:出站端口由核心业务域定义,由从适配器实现,核心域只依赖接口不依赖实现。

  4. 业务逻辑唯一归属:所有业务规则必须放在核心业务域,适配器仅负责格式与协议转换。

落地实现

核心业务域-领域实体
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年提出,在六边形架构的基础上进一步细化了系统内部圈层,提出了绝对依赖规则:圈层之间的依赖必须从外向内,内层代码绝对不能知道外层的任何信息,包括类名、方法名、注解、常量等。

整洁架构通过圈层划分,将业务核心与技术实现彻底隔离,确保业务核心是系统最稳定的部分,而易变化的技术实现放在最外层,实现高内聚、低耦合的设计目标。从内到外分为四个核心圈层:

  1. 实体层:也叫企业业务规则层,包含核心业务实体、值对象、领域服务,封装最核心的业务规则,是系统最稳定的部分。

  2. 用例层:也叫应用业务规则层,包含系统用例实现,编排业务流程,实现应用级业务规则,仅依赖实体层。

  3. 接口适配器层:负责内外数据格式的转换,对应六边形架构的适配器层,包含Controller、仓储实现、网关实现等。

  4. 框架与驱动层:最外层,包含所有框架、工具、数据库、第三方接口等技术实现,代码变化最频繁,不包含任何业务逻辑。

核心设计规范

  1. 绝对依赖规则:任何代码都不能依赖外层圈层,内层绝对不能引用外层的任何类、方法、注解。

  2. 业务规则唯一归属:企业级核心业务规则仅能放在实体层,应用级业务流程仅能放在用例层,外层不能包含任何业务规则。

  3. 数据格式隔离:每个圈层使用独立的数据模型,禁止跨圈层共享数据模型,层间必须通过适配器进行数据转换。

  4. 异常处理规则:内层抛出的业务异常由外层统一捕获处理,内层不能处理外层异常,也不能感知外层异常类型。

落地实现

实体层-值对象
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系统,低复杂度业务 中等复杂度业务,需频繁替换外部依赖 高复杂度企业级系统,多团队协作

选型核心原则

  1. 架构复杂度必须与业务复杂度匹配,没有最好的架构,只有最合适的架构。

  2. 简单CRUD管理系统、团队成员水平参差不齐,优先选择分层架构,足够支撑业务且上手成本低。

  3. 业务规则相对复杂、需要对接多种外部系统、多端接入的场景,优先选择六边形架构,平衡复杂度与可维护性。

  4. 企业级核心业务系统、业务规则复杂、生命周期长、多团队协作的场景,优先选择整洁架构,保障系统的长期可维护性。

六、架构落地的核心避坑指南

分层架构常见坑

  • 跨层调用:Controller直接调用Mapper,跳过Service层,导致业务逻辑分散无法统一管控。

  • 反向依赖:Service调用Controller方法、Mapper调用Service方法,导致循环依赖,架构崩溃。

  • 职责越界:Controller或Mapper中编写业务逻辑,导致Service层空壳化,业务逻辑分散。

  • 模型混用:将数据库DO直接传到前端,导致表结构泄露,层间耦合严重。

六边形架构常见坑

  • 业务逻辑泄露:将业务逻辑写到适配器中,导致核心域空壳化,架构失效。

  • 端口定义混乱:入站与出站端口不分,单个端口定义过多方法,违反单一职责原则。

  • 核心域依赖外部:核心域引入外部框架的类与注解,破坏了内外隔离的核心设计。

  • 适配器职责越界:适配器中加入业务判断,而非仅做格式与协议转换。

整洁架构常见坑

  • 违反依赖规则:内层引用外层的类、方法、注解,破坏绝对依赖规则,架构优势荡然无存。

  • 圈层职责混淆:应用级业务流程写入实体层,核心业务规则写入用例层,导致业务逻辑分散。

  • 数据模型混用:跨圈层共享数据模型,导致层间耦合严重。

  • 过度设计:简单CRUD系统强行使用整洁架构,导致代码冗余,开发效率大幅降低。

总结

架构设计的本质,是解决业务复杂度带来的代码耦合、迭代困难、维护成本高的问题。分层、六边形、整洁架构三者并非对立,而是一脉相承的演进关系,核心都围绕「关注点分离」与「依赖反转」这两个最本质的设计原则。

对于开发者而言,无需盲目追求高大上的架构,而是要根据业务复杂度、团队水平、系统生命周期选择最合适的架构。只有真正理解架构的底层逻辑,才能写出高内聚、低耦合、可维护、可扩展的代码,从根本上避免陷入屎山代码的困境。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐