后端代码分层架构怎样设计才更易维护

chinaz
chinaz 正式会员超兽战士
发布于 2026-10-07 10:40 ·0 浏览 ·0 回复

结论先行:后端分层要易维护,靠的不是分几层,而是三条硬约束——每层只承担一类职责、依赖只能单向向内、跨层只认接口不认实现。落到工程上,业务系统用「接口层 → 应用层 → 领域层 → 基础设施层」四层结构,再配 ArchUnit 架构守护测试,就能把长期维护成本压住。

后端为什么要分层?不分层会怎样

分层的唯一目的是隔离变化:让业务规则的改动只影响领域层,让换数据库、换缓存只影响基础设施层。

不分层的典型症状是 Service 类涨到 2000 行、SQL 散落在 Controller 里、改一个字段要动 8 个文件。这些都不是「代码风格」问题,而是职责边界被击穿后,任何一处改动都会向外扩散。

判断分层是否合格的实操标准:任何一个需求变更,只允许修改 1 个层的代码,最多再加一次 DTO 字段映射。如果需要同时改 Controller、Service 和 DAO,说明分层只是目录形式,没有形成依赖约束。

三层架构和领域驱动四层架构有什么区别

结论:三层(Controller-Service-DAO)按技术职责切分,四层按业务职责切分;前者适合 CRUD 为主的系统,后者适合业务规则复杂、需要长期演进的核心系统。

三层结构的问题是 Service 同时干了三件事:编排流程、写业务规则、拼装数据访问,导致业务规则无法脱离数据库被单测。领域驱动设计(DDD,一种以业务模型为中心的设计方法)把 Service 拆成两层:应用层只做流程编排和事务控制,领域层只放业务规则,不依赖任何框架和数据库。

一个可落地的包结构:

com.example.order
├── interfaces      // Controller、DTO、参数校验
├── application     // AppService、事务边界、编排
├── domain          // Entity、ValueObject、DomainService、Repository 接口
└── infrastructure  // MyBatis/JPA 实现、Redis、MQ、外部 HTTP 客户端

各层到底应该放什么代码

结论:接口层只做协议转换与参数校验,应用层只做编排与事务,领域层只放业务规则,基础设施层只做技术实现。任何一层越界,都算架构违规。

  • 接口层:接收 HTTP 请求、校验参数(@Valid)、把 DTO 转成应用层入参、组装响应。禁止出现 SQL、禁止出现 @Transactional。
  • 应用层:调用领域对象完成用例,事务注解只加在这一层的 public 方法上。禁止写 if-else 业务分支。
  • 领域层:实体、值对象、领域服务、领域事件。禁止 import javax.servlet、org.springframework.web、java.sql。
  • 基础设施层:实现领域层定义的 Repository 接口,封装 ORM、缓存、消息队列。

层与层之间怎么传数据?要不要每层都建对象

结论:跨层传 DTO,不要让数据库实体(Entity/DO)穿透到接口层,否则一个表字段变更会直接改变对外 API。

实践规则有三条:接口层用 Request/Response DTO;应用层用 Command/Query 对象;领域层用领域实体。转换用 MapStruct 在编译期生成映射代码,比反射式 BeanUtils 快一个数量级,且字段漏映射会在编译时报错。

若字段较少,手工写转换方法也完全可以——多写 30 行代码换取字段变更时可被 IDE 追踪,比省事的反射工具更划算。

依赖方向怎么保证?怎样防止半年后代码腐化

结论:依赖倒置(高层模块定义接口,低层模块实现)+ ArchUnit 自动化测试,是唯一能长期守住边界的组合。

依赖倒置的落地形式是:Repository 接口定义在 domain 层,MyBatis/JPA 实现写在 infrastructure 层。domain 层编译时不依赖任何持久化框架。

在 Maven 项目中引入 ArchUnit 并写一条分层规则测试:

@ArchTest
static final ArchRule layers = layeredArchitecture()
    .layer("Interfaces").definedBy("..interfaces..")
    .layer("Application").definedBy("..application..")
    .layer("Domain").definedBy("..domain..")
    .layer("Infrastructure").definedBy("..infrastructure..")
    .whereLayer("Interfaces").mayNotBeAccessedByAnyLayer()
    .whereLayer("Domain").mayOnlyBeAccessedByLayers("Application", "Infrastructure");

执行 mvn test -Dtest=*ArchitectureTest,任何人写了跨层调用,CI 直接红灯。这比写十页架构文档有效。

事务、异常、日志分别放在哪一层

结论:事务放在应用层方法入口,异常在接口层统一兜底,日志在接口层入口和基础设施层出口打点。

事务边界跟随用例,因此 @Transactional 只标注在应用层方法上,且只标注 public 方法(Spring 的 AOP 代理对 private 方法和同类内部调用不生效,这是线上事务失效最常见的两个原因)。异常处理用 @RestControllerAdvice 捕获业务异常和系统异常,转换为统一的错误码响应体,领域层只抛业务异常、不处理 HTTP 状态码。日志遵循「一处入口、一处出口」:接口层打请求参数与耗时,基础设施层打 SQL 与外部调用耗时,中间层不重复打印。

把上面几条收拢:分层的价值来自约束而非目录,职责单一、单向依赖、接口隔离是三条底线;四层结构 + DTO 隔离 + ArchUnit 守护测试,构成一套能扛住两三年演进的最小可维护方案。

版权声明:本文来自 GJ站长论坛《后端代码分层架构怎样设计才更易维护》
原文链接:https://www.gj0.com/thread-238.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~