吃透 3 大核心架构模式:分层、六边形、整洁架构,从底层逻辑到落地实现全解

简介: 本文详解Java企业级三大核心架构:分层、六边形与整洁架构。三者一脉相承,均围绕“关注点分离”与“依赖反转”本质原则,分别适配简单CRUD、多端集成及高复杂度长周期系统。附落地代码、避坑指南与选型策略,助开发者摆脱耦合困境,提升可维护性与可测试性。

很多开发者在做业务系统时,常常陷入代码耦合、需求迭代困难、单测无法落地的困境,本质上是没有选对合适的架构模式,或是对架构的核心设计理解不到位。分层、六边形、整洁架构是企业级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系统强行使用整洁架构,导致代码冗余,开发效率大幅降低。

总结

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

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

目录
相关文章
|
3天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
10439 44
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
22天前
|
人工智能 JavaScript Ubuntu
5分钟上手龙虾AI!OpenClaw部署(阿里云+本地)+ 免费多模型配置保姆级教程(MiniMax、Claude、阿里云百炼)
OpenClaw(昵称“龙虾AI”)作为2026年热门的开源个人AI助手,由PSPDFKit创始人Peter Steinberger开发,核心优势在于“真正执行任务”——不仅能聊天互动,还能自动处理邮件、管理日程、订机票、写代码等,且所有数据本地处理,隐私完全可控。它支持接入MiniMax、Claude、GPT等多类大模型,兼容微信、Telegram、飞书等主流聊天工具,搭配100+可扩展技能,成为兼顾实用性与隐私性的AI工具首选。
23542 121
|
8天前
|
人工智能 JavaScript API
解放双手!OpenClaw Agent Browser全攻略(阿里云+本地部署+免费API+网页自动化场景落地)
“让AI聊聊天、写代码不难,难的是让它自己打开网页、填表单、查数据”——2026年,无数OpenClaw用户被这个痛点困扰。参考文章直击核心:当AI只能“纸上谈兵”,无法实际操控浏览器,就永远成不了真正的“数字员工”。而Agent Browser技能的出现,彻底打破了这一壁垒——它给OpenClaw装上“上网的手和眼睛”,让AI能像真人一样打开网页、点击按钮、填写表单、提取数据,24小时不间断完成网页自动化任务。
2195 5