十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot项目重构实战:从混沌代码到清晰架构的演进之路

Spring Boot项目重构实战:从混沌代码到清晰架构的演进之路 最近在折腾一个项目看着满屏的代码和日志我陷入了沉思我到底做了个什么东西啊啊啊啊啊相信很多开发者朋友在项目攻坚期面对复杂的业务逻辑、层层嵌套的依赖和不断涌现的 Bug 时都有过类似的灵魂拷问。这种感觉就像是在一片混沌中摸索代码写了不少功能也堆叠了很多但项目的整体轮廓和核心价值却越来越模糊。本文正是源于这样一次深刻的“项目复盘焦虑”。我将以一个典型的、功能逐渐“膨胀”的后台管理系统为例完整拆解从需求混沌到架构清晰的全过程。我们会一起梳理如何识别代码中的“坏味道”如何使用设计模式进行重构并最终搭建一个可维护、易扩展的 Spring Boot 项目骨架。无论你是正在为遗留系统焦头烂额的初级开发者还是希望提升工程化思维的中高级工程师这篇从“混沌”到“秩序”的实战笔记都能提供直接的参考。1. 项目背景与“混沌”现状分析我们从一个常见的场景开始公司需要一个内部使用的“员工信息管理平台”。最初的版本可能非常简单。1.1 初始需求与快速实现老板说“先做个能查员工信息和录入请假的功能。” 于是一个典型的“速成”项目诞生了。为了赶时间我们可能会写出类似下面的代码结构项目结构初始版employee-manager/ ├── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ └── employeemanager/ │ │ ├── EmployeeController.java // 控制层 │ │ ├── EmployeeService.java // 服务层 │ │ ├── Employee.java // 实体类 │ │ ├── EmployeeRepository.java // 数据访问层JPA │ │ ├── LeaveController.java // 请假控制层 │ │ ├── LeaveService.java // 请假服务层 │ │ └── Leave.java // 请假实体类 │ └── resources/ │ ├── application.properties │ └── ...数据库配置 └── pom.xml核心代码示例EmployeeService.java - 初始版Service public class EmployeeService { Autowired private EmployeeRepository employeeRepository; Autowired private EmailSender emailSender; // 一个直接注入的邮件发送类 Autowired private ReportGenerator reportGenerator; // 一个直接注入的报告生成类 public Employee getEmployeeById(Long id) { // 直接调用Repository return employeeRepository.findById(id).orElse(null); } public void applyLeave(LeaveRequest request) { // 1. 验证员工状态 Employee emp employeeRepository.findById(request.getEmployeeId()).orElseThrow(...); if (!ACTIVE.equals(emp.getStatus())) { throw new RuntimeException(员工状态异常无法请假); } // 2. 业务逻辑检查假期余额等这里混杂了大量逻辑 int usedLeaveDays calculateUsedLeaveDays(emp.getId()); if (request.getDays() (emp.getTotalLeaveDays() - usedLeaveDays)) { throw new RuntimeException(假期余额不足); } // 3. 保存请假记录直接操作另一个实体 Leave leave new Leave(); leave.setEmployeeId(emp.getId()); leave.setStartDate(request.getStartDate()); // ... 其他字段设置 // 注意这里没有LeaveRepository假设直接在Service里用JPA保存 // entityManager.persist(leave); // 4. 发送通知邮件紧耦合 emailSender.sendEmail(emp.getEmail(), 您的请假申请已提交, 请假详情...); // 5. 生成月度报告为何在这里 reportGenerator.generateMonthlyReport(emp.getDepartment()); } // 一个私有方法可能越来越复杂 private int calculateUsedLeaveDays(Long employeeId) { // 直接写SQL或调用其他Repository // 逻辑复杂且无法复用 return 0; } }1.2 “混沌”的典型特征随着需求增加“再加个绩效考核模块”、“对接OA系统审批流”、“导出数据要支持多种格式”上面的代码会迅速“膨胀”并出现以下问题单一职责原则被破坏EmployeeService变成了一个“上帝类”既管员工信息又管请假流程还负责发邮件、生成报告。紧耦合服务层直接依赖具体的邮件发送 (EmailSender) 和报告生成 (ReportGenerator) 实现。想换一个邮件服务商需要修改所有调用的地方。代码重复计算假期余额的逻辑可能在其他服务如AttendanceService中又被复制粘贴了一遍。可测试性差由于紧耦合想要单元测试applyLeave方法你必须同时 Mock 数据库、邮件服务和报告服务测试变得极其笨重。扩展困难老板说“请假成功后不仅要发邮件还要发企业微信消息。” 你就得去修改applyLeave方法添加新的依赖和调用违反了开闭原则。这就是“我到底做了个什么东西”的根源项目没有清晰的架构边界代码像一团纠缠在一起的毛线任何改动都牵一发而动全身开发者的心智负担极重。2. 重构目标与核心设计模式引入我们的目标是将这个“混沌”的系统重构成一个层次清晰、职责分明、易于扩展的架构。我们会引入几个关键的设计模式和架构思想。2.1 目标架构概览重构后的项目应该遵循经典的分层架构并引入领域驱动设计DDD的一些概念精简版和设计模式employee-manager-refactored/ ├── src/main/java/com/example/employeemanager/ │ ├── common/ # 通用组件 │ │ ├── exception/ # 全局异常处理 │ │ ├── response/ # 统一响应体 │ │ └── utils/ # 工具类 │ ├── config/ # 配置类如Swagger, 线程池 │ ├── domain/ # 领域层核心业务逻辑 │ │ ├── model/ # 领域实体/值对象 │ │ ├── service/ # 领域服务纯业务逻辑无依赖 │ │ └── event/ # 领域事件 │ ├── application/ # 应用层协调领域服务事务边界 │ │ ├── service/ # 应用服务 │ │ └── dto/ # 数据传输对象 │ ├── infrastructure/ # 基础设施层技术实现细节 │ │ ├── persistence/ # 持久化JPA实现 │ │ ├── client/ # 外部服务客户端邮件、消息 │ │ └── message/ # 消息队列发送 │ └── interfaces/ # 接口层对外暴露 │ ├── web/ # Web控制器RESTful API │ └── assembler/ # DTO与领域对象转换器 └── ...2.2 核心设计模式应用场景策略模式Strategy Pattern用于处理多种类型的报告生成PDF、Excel、CSV或消息通知邮件、微信、短信。工厂模式Factory Pattern用于根据类型创建不同的策略实例。观察者模式/领域事件Observer Pattern / Domain Event用于解耦核心业务逻辑与副作用操作。例如员工请假成功这个“事件”发生后监听该事件的处理器可以异步去发送邮件和消息而不是在请假方法内同步调用。依赖注入Dependency InjectionSpring 核心用于管理对象生命周期和解耦。门面模式Facade Pattern应用服务层可以作为门面对外提供粗粒度的业务接口内部协调多个领域服务。3. 环境准备与项目搭建我们使用 Spring Boot 2.7.x 和 Java 11 作为基准环境。确保你的开发环境已准备好。3.1 基础环境与依赖操作系统Windows 10/11, macOS, Linux 均可。JDK11 或 17LTS版本。构建工具Maven 3.6 或 Gradle。IDEIntelliJ IDEA推荐或 Eclipse。核心 Maven 依赖 (pom.xml)?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version !-- 使用稳定的版本 -- relativePath/ /parent groupIdcom.example/groupId artifactIdemployee-manager-refactored/artifactId version0.0.1-SNAPSHOT/version nameemployee-manager-refactored/name descriptionRefactored Employee Management System/description properties java.version11/java.version /properties dependencies !-- Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Data JPA -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency !-- 数据库以H2内存数据库为例方便演示 -- dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency !-- Lombok简化代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- 测试 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency !-- 可选用于参数校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- 可选用于领域事件Spring自带 -- !-- spring-boot-starter-data-jpa 已包含相关事务支持 -- /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build /project基础配置 (application.yml)spring: application: name: employee-manager datasource: url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY-1;DB_CLOSE_ON_EXITFALSE driver-class-name: org.h2.Driver username: sa password: jpa: hibernate: ddl-auto: update # 开发环境生产环境请使用validate或none配合Flyway/Liquibase show-sql: true properties: hibernate: format_sql: true h2: console: enabled: true # 开启H2控制台访问 http://localhost:8080/h2-console path: /h2-console server: port: 8080 logging: level: com.example.employeemanager: DEBUG4. 核心重构实战从“混沌”到“清晰”我们将一步步重构最初的EmployeeService应用之前提到的模式。4.1 第一步定义清晰的领域模型首先将核心业务概念抽象成领域实体和值对象放在domain/model/下。Employee.java (领域实体)package com.example.employeemanager.domain.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.*; import java.time.LocalDate; Entity Table(name employees) Data NoArgsConstructor AllArgsConstructor public class Employee { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String name; private String email; private String department; Enumerated(EnumType.STRING) private EmployeeStatus status; // 枚举ACTIVE, INACTIVE private Integer totalLeaveDays; // 值对象可以嵌入这里简化 private LocalDate joinDate; public enum EmployeeStatus { ACTIVE, INACTIVE } // 领域方法判断是否可请假 public boolean canApplyLeave() { return this.status EmployeeStatus.ACTIVE; } }LeaveApplication.java (领域实体 - 请假申请)package com.example.employeemanager.domain.model; import lombok.Data; import javax.persistence.*; import java.time.LocalDate; Entity Table(name leave_applications) Data public class LeaveApplication { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private Long employeeId; private LocalDate startDate; private LocalDate endDate; private Integer days; Enumerated(EnumType.STRING) private LeaveStatus status; // PENDING, APPROVED, REJECTED private String reason; public enum LeaveStatus { PENDING, APPROVED, REJECTED } }4.2 第二步引入领域服务与领域事件将核心的、不依赖于外部资源的业务逻辑放入领域服务。同时使用领域事件来解耦。LeavePolicyService.java (领域服务)package com.example.employeemanager.domain.service; import com.example.employeemanager.domain.model.Employee; import com.example.employeemanager.domain.model.LeaveApplication; import org.springframework.stereotype.Service; import javax.transaction.Transactional; /** * 请假策略领域服务。 * 只包含核心业务规则不依赖外部系统如邮件、报告。 */ Service Transactional // 事务放在领域服务或应用服务层视情况而定 public class LeavePolicyService { /** * 验证请假申请是否合法。 * param employee 员工 * param application 请假申请 * param usedLeaveDays 已使用的假期天数 * throws IllegalArgumentException 如果验证不通过 */ public void validateLeaveApplication(Employee employee, LeaveApplication application, Integer usedLeaveDays) { // 规则1员工状态必须为活跃 if (!employee.canApplyLeave()) { throw new IllegalArgumentException(员工状态异常无法请假); } // 规则2请假天数不能超过剩余假期 int remainingDays employee.getTotalLeaveDays() - usedLeaveDays; if (application.getDays() remainingDays) { throw new IllegalArgumentException(假期余额不足剩余天数: remainingDays); } // 规则3开始日期不能晚于结束日期已在DTO或实体中校验 // ... 可以添加更多业务规则 } /** * 计算剩余假期。 * 这是一个纯计算逻辑属于领域知识。 */ public int calculateRemainingLeaveDays(Integer totalDays, Integer usedDays) { return totalDays - usedDays; } }LeaveAppliedEvent.java (领域事件)package com.example.employeemanager.domain.event; import lombok.AllArgsConstructor; import lombok.Data; import java.time.LocalDateTime; /** * 请假申请已提交的领域事件。 * 事件是过去式命名通常使用过去分词。 */ Data AllArgsConstructor public class LeaveAppliedEvent { private Long employeeId; private String employeeName; private String employeeEmail; private Long leaveApplicationId; private Integer leaveDays; private LocalDateTime occurredOn; }4.3 第三步基础设施层 - 实现外部依赖我们将邮件发送、消息通知等外部系统交互放在基础设施层并通过接口抽象。NotificationService.java (接口 - 在domain或application层定义)package com.example.employeemanager.application.service; // 放在应用层接口包 /** * 通知服务接口。 * 领域层和应用层依赖此抽象而非具体实现。 */ public interface NotificationService { void sendLeaveAppliedNotification(String toEmail, String employeeName, Integer leaveDays); }EmailNotificationServiceImpl.java (基础设施层实现)package com.example.employeemanager.infrastructure.client; import com.example.employeemanager.application.service.NotificationService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** * 邮件通知的具体实现。 * 这里可以集成JavaMailSender、第三方邮件SDK等。 */ Service(emailNotificationService) // 指定Bean名称便于策略模式使用 Slf4j public class EmailNotificationServiceImpl implements NotificationService { Override public void sendLeaveAppliedNotification(String toEmail, String employeeName, Integer leaveDays) { // 模拟发送邮件 String subject 请假申请提交成功; String content String.format(尊敬的%s您的%d天请假申请已提交正在审批中。, employeeName, leaveDays); log.info(发送邮件到 {}: 主题-{} 内容-{}, toEmail, subject, content); // 实际调用 mailSender.send(...); } }WeChatNotificationServiceImpl.java (另一个实现)package com.example.employeemanager.infrastructure.client; import com.example.employeemanager.application.service.NotificationService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; Service(wechatNotificationService) Slf4j public class WeChatNotificationServiceImpl implements NotificationService { Override public void sendLeaveAppliedNotification(String toEmail, String employeeName, Integer leaveDays) { // 模拟发送企业微信消息 log.info(发送企业微信消息给员工{}: 您的{}天请假申请已提交。, employeeName, leaveDays); } }4.4 第四步应用服务层 - 协调与编排应用服务作为门面负责协调多个领域服务处理事务发布领域事件并调用基础设施层。LeaveApplicationService.java (应用服务)package com.example.employeemanager.application.service; import com.example.employeemanager.domain.event.LeaveAppliedEvent; import com.example.employeemanager.domain.model.Employee; import com.example.employeemanager.domain.model.LeaveApplication; import com.example.employeemanager.domain.service.LeavePolicyService; import com.example.employeemanager.infrastructure.persistence.EmployeeRepository; import com.example.employeemanager.infrastructure.persistence.LeaveApplicationRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; Service RequiredArgsConstructor // Lombok生成构造器用于依赖注入 Slf4j public class LeaveApplicationService { private final EmployeeRepository employeeRepository; private final LeaveApplicationRepository leaveApplicationRepository; private final LeavePolicyService leavePolicyService; private final ApplicationEventPublisher eventPublisher; // Spring事件发布器 // 注意这里不直接依赖具体的NotificationService而是通过事件监听 /** * 提交请假申请 - 应用服务方法 * param employeeId 员工ID * param startDate 开始日期 * param endDate 结束日期 * param reason 原因 * return 创建的请假申请ID */ Transactional public Long applyForLeave(Long employeeId, LocalDateTime startDate, LocalDateTime endDate, String reason) { // 1. 获取领域实体 Employee employee employeeRepository.findById(employeeId) .orElseThrow(() - new RuntimeException(员工不存在)); // 2. 计算已使用假期这里调用一个查询服务为了演示假设已有 Integer usedLeaveDays leaveApplicationRepository.sumApprovedLeaveDaysByEmployee(employeeId); // 3. 创建请假申请领域对象 LeaveApplication application new LeaveApplication(); application.setEmployeeId(employeeId); application.setStartDate(startDate.toLocalDate()); application.setEndDate(endDate.toLocalDate()); // 计算天数简化 long days java.time.temporal.ChronoUnit.DAYS.between(startDate.toLocalDate(), endDate.toLocalDate()) 1; application.setDays((int) days); application.setReason(reason); application.setStatus(LeaveApplication.LeaveStatus.PENDING); // 4. 调用领域服务进行业务规则校验 leavePolicyService.validateLeaveApplication(employee, application, usedLeaveDays); // 5. 持久化请假申请 LeaveApplication savedApplication leaveApplicationRepository.save(application); log.info(请假申请已保存ID: {}, savedApplication.getId()); // 6. 发布领域事件解耦关键步骤 LeaveAppliedEvent event new LeaveAppliedEvent( employee.getId(), employee.getName(), employee.getEmail(), savedApplication.getId(), savedApplication.getDays(), LocalDateTime.now() ); eventPublisher.publishEvent(event); // 发布事件触发后续监听器 // 7. 返回结果 return savedApplication.getId(); } }4.5 第五步事件监听器 - 处理副作用创建监听器来响应LeaveAppliedEvent执行发送邮件、生成报告等非核心业务逻辑。LeaveAppliedEventListener.javapackage com.example.employeemanager.interfaces.listener; import com.example.employeemanager.application.service.NotificationService; import com.example.employeemanager.domain.event.LeaveAppliedEvent; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; // 可选异步处理 import org.springframework.stereotype.Component; import java.util.List; Component RequiredArgsConstructor Slf4j public class LeaveAppliedEventListener { // 注入所有的通知服务策略模式集合 private final ListNotificationService notificationServices; // 或者注入特定的服务这里演示策略集合 // private final NotificationService emailNotificationService; // private final NotificationService wechatNotificationService; /** * 监听请假申请事件发送通知。 * 使用Async实现异步避免阻塞主业务流程。 */ Async // 需要配置EnableAsync EventListener public void handleLeaveAppliedEvent(LeaveAppliedEvent event) { log.info(监听到请假申请事件: {}, event); // 策略模式遍历所有通知服务并执行 for (NotificationService service : notificationServices) { try { service.sendLeaveAppliedNotification( event.getEmployeeEmail(), event.getEmployeeName(), event.getLeaveDays() ); } catch (Exception e) { log.error(发送通知失败服务: {}, 错误: {}, service.getClass().getSimpleName(), e.getMessage()); // 根据业务决定是重试、记录日志还是忽略 } } // 这里也可以调用ReportGenerator生成报告 // reportGenerator.generateMonthlyReport(...); } }启用异步支持 (AsyncConfig.java)package com.example.employeemanager.config; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; Configuration EnableAsync public class AsyncConfig { // 可以配置线程池等 }4.6 第六步Web接口层 - 对外暴露API最后我们创建干净、简洁的Controller它只负责接收请求、调用应用服务、返回响应。LeaveController.javapackage com.example.employeemanager.interfaces.web; import com.example.employeemanager.application.service.LeaveApplicationService; import com.example.employeemanager.interfaces.web.dto.LeaveApplyRequest; import com.example.employeemanager.interfaces.web.dto.ApiResponse; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; RestController RequestMapping(/api/leaves) RequiredArgsConstructor public class LeaveController { private final LeaveApplicationService leaveApplicationService; PostMapping(/apply) public ApiResponseLong applyForLeave(Valid RequestBody LeaveApplyRequest request) { Long applicationId leaveApplicationService.applyForLeave( request.getEmployeeId(), request.getStartDate(), request.getEndDate(), request.getReason() ); return ApiResponse.success(applicationId); } }LeaveApplyRequest.java (DTO)package com.example.employeemanager.interfaces.web.dto; import lombok.Data; import javax.validation.constraints.NotNull; import java.time.LocalDateTime; Data public class LeaveApplyRequest { NotNull(message 员工ID不能为空) private Long employeeId; NotNull(message 开始时间不能为空) private LocalDateTime startDate; NotNull(message 结束时间不能为空) private LocalDateTime endDate; private String reason; }ApiResponse.java (统一响应体)package com.example.employeemanager.interfaces.web.dto; import lombok.Data; Data public class ApiResponseT { private boolean success; private String message; private T data; private String code; // 可扩展错误码 public static T ApiResponseT success(T data) { ApiResponseT response new ApiResponse(); response.setSuccess(true); response.setMessage(success); response.setData(data); return response; } public static T ApiResponseT error(String message) { ApiResponseT response new ApiResponse(); response.setSuccess(false); response.setMessage(message); return response; } }5. 运行与验证启动应用运行EmployeeManagerRefactoredApplication的 main 方法。初始化数据可选可以写一个CommandLineRunner或使用data.sql插入测试员工数据。调用API使用 Postman 或 curl 测试请假接口。POST http://localhost:8080/api/leaves/apply Content-Type: application/json { employeeId: 1, startDate: 2024-05-27T09:00:00, endDate: 2024-05-28T18:00:00, reason: 身体不适 }观察日志控制台应依次打印“请假申请已保存ID: ...”“监听到请假申请事件: ...”“发送邮件到 ...”“发送企业微信消息给员工 ...”6. 重构前后对比与核心收益维度重构前混沌重构后清晰职责划分EmployeeService大而全承担过多职责。领域服务、应用服务、基础设施层各司其职。耦合度高度耦合邮件、报告逻辑硬编码在业务方法中。松耦合通过接口和领域事件解耦核心业务逻辑纯净。可测试性难以单元测试需要 Mock 大量外部依赖。领域服务可独立测试应用服务通过 Mock 领域服务和事件发布器测试监听器可单独测试。可维护性修改一处如换邮件服务可能影响多处。修改通知方式只需新增/替换NotificationService实现或修改监听器。业务规则修改在领域服务内。可扩展性新增一个通知渠道如短信需修改核心业务代码。新增通知渠道只需实现NotificationService接口并在监听器中自动生效或通过配置选择。代码复用业务规则如假期计算散落在各处。业务规则集中在领域服务可被多个应用服务复用。核心收益总结清晰度通过分层和模式代码结构一目了然“我到底在做什么”变得清晰。灵活性应对需求变更如新增审批流、新的通知方式更加从容。可维护性每个类/方法职责单一便于阅读、修改和调试。可测试性模块边界清晰便于编写单元测试和集成测试。7. 常见问题与排查思路在按照上述架构实践时你可能会遇到以下问题问题现象可能原因解决思路领域事件未触发1. 事件监听器未被 Spring 管理缺少Component。2. 事件发布和监听不在同一个事务上下文中默认在事务提交后发布。3.Async未生效未加EnableAsync。1. 检查监听器类是否有Component。2. 检查事件发布是否在事务方法内理解TransactionalEventListener的相位。3. 检查主类或配置类是否有EnableAsync。依赖注入失败1. 接口有多个实现未指定Qualifier。2. 包扫描未覆盖到相关类。3. 循环依赖。1. 使用Qualifier(beanName)指定注入哪个实现。2. 确保SpringBootApplication主类在根包或使用ComponentScan。3. 使用Lazy或重构代码消除循环依赖。事务不生效1. 方法不是public。2. 在同一个类内部调用带Transactional的方法。3. 异常类型未被回滚默认只回滚RuntimeException和Error。1. 确保事务方法是public。2. 通过代理对象调用如从 Spring 容器获取 Bean 再调用。3. 使用Transactional(rollbackFor Exception.class)。H2 控制台无法访问1.spring.h2.console.enabled未设置为true。2. 路径配置错误。3. 安全配置拦截。1. 检查application.yml配置。2. 默认路径是/h2-console。3. 如果是 Spring Security 项目需放行该路径。8. 最佳实践与工程建议领域驱动设计DDD的适度应用对于复杂核心业务如电商交易、风控深入使用聚合根、仓储、领域服务。对于简单 CRUD过度设计反而增加复杂度。本文是“精简版DDD”适合大多数后台管理系统。依赖方向永远保持高层模块领域层、应用层不依赖低层模块基础设施层的具体实现而是依赖其抽象接口。这是依赖倒置原则。领域事件的合理使用事件用于解耦核心业务与副作用如发送通知、更新冗余数据、记录审计日志。对于强一致性要求的操作如扣减库存仍需在事务内完成。异常处理定义清晰的业务异常体系如BusinessException在应用层或全局异常处理器中统一捕获并转换为友好的 API 响应。日志记录在关键业务节点如领域事件发布、外部服务调用记录 INFO 级别日志在异常处记录 ERROR 级别日志并带上上下文信息。配置管理将邮件服务器地址、消息队列连接等外部依赖配置放在application.yml或 Apollo/Nacos 中与代码分离。测试策略领域服务重点进行单元测试验证业务规则。应用服务进行集成测试Mock 仓储和外部服务。API 层进行端到端测试如使用SpringBootTest和TestRestTemplate。代码审查关注点审查时除了功能正确性更要关注架构边界是否被破坏、是否有新的“上帝类”产生、依赖关系是否合理。通过这样一套从“混沌”到“清晰”的重构过程我们不仅得到了一个结构良好的项目更重要的是建立了一套应对复杂性的思维框架。下次当你再面对一团乱麻的代码感觉“我到底做了个什么东西”时不妨停下来从识别职责、解耦依赖、定义边界开始一步步将它梳理清晰。这个过程本身就是工程师成长中最有价值的修炼。
返回列表