1. 后端分层架构规范
目标
在真实复杂业务下,保持依赖方向清晰,避免 Service 互相纠缠,保证系统可长期演进。
1.1. 分层总览(必须遵守)
java
Controller
↓
Service / Facade(业务主体 / 流程编排)
↓
Query(只读查询模型,允许 JOIN)
↓
Dao(单表持久化)
↓
Table
依赖只能自上而下,禁止反向。
1.2. 核心概念定义(统一口径)
1.2.1. 业务主体(Domain Entity)
具有独立业务语义和生命周期的对象,例如:
- 用户、订单、库存、账户、商品
一个业务主体 = 一个 Service
1.2.2. 附属表(附属/技术性数据)
不具备独立业务语义,仅服务于某一主体:
- 日志、流水、历史表、快照、审计表
可以由主体 Service 直接操作
1.2.3. 跨主体流程
涉及 两个及以上业务主体 的写操作:
- 下单扣库存
- 订单 + 支付
- 用户 + 账户初始化
必须上移到 Facade / Flow Service
1.3. 各层职责与硬规则
1.3.1. Controller 层(入口层)
职责
- 参数校验
- 权限校验
- 调用 Service / Facade
禁止
- ❌ 写业务逻辑
- ❌ 访问 Dao / Query
- ❌ Controller 互调
1.3.2. Service 层(业务主体层)
职责
- 本业务主体的业务规则
- 状态变更
- 操作本主体表及其附属表
允许
- ✅ 写:本主体 Dao
- ✅ 写:本主体附属表 Dao(日志 / 流水)
- ✅ 读:Query(用于校验 / 展示 / 计算)
禁止
- ❌ 写其他业务主体表
- ❌ 调用其他业务主体 Service
- ❌ 编写 JOIN SQL
判断口诀
"如果删掉另一个业务主体,这个方法是否仍成立?"
- 是 → 留在本 Service
- 否 → 上移 Facade
1.3.3. Facade / Flow Service(流程编排层)
职责
- 跨业务主体流程
- 调用顺序
- 事务边界
- 异常与失败处理
允许
- ✅ 调用多个 Service
- ✅ 管理事务
禁止
- ❌ 直接访问 Dao
- ❌ 编写 SQL
- ❌ 实现领域细节规则
1.3.4. Query 层(只读模型,JOIN 主要归属)
职责
- 多表查询
- 列表 / 详情 / 统计 / 报表
- 性能优化查询
允许
- ✅ JOIN 多表
- ✅ 跨业务主体
- ✅ 返回 DTO / View Object
禁止
- ❌ 写操作(INSERT / UPDATE / DELETE)
- ❌ 承载业务规则
- ❌ 修改业务状态
命名建议
XxxOnlyQueryXxxReadModelXxxQueryRepository
1.3.5. Dao 层(持久化层)
职责
- 单表 CRUD
- 与表结构一一对应
规则
- ❌ 不允许 JOIN 多个业务主体表
- ❌ 不承载业务语义
- ❌ 不被跨业务主体复用
1.4. JOIN SQL 放置规则(强制执行)
一句话规则(写在 Code Review 标准里)
凡是 JOIN 了多个业务主体的 SQL,一律不进主体 Dao。
1.4.1. JOIN SQL 决策表(必须按顺序判断)
❓1️⃣ 是否跨多个业务主体?
- 是 → Query
- 否 → 继续
❓2️⃣ 是否只读?
- 是 → Query
- 否 → ❌ 拆为 Service 调用(禁止 JOIN 写)
❓3️⃣ 是否承载业务规则?
- 是 → ❌ 不允许(回 Service)
- 否 → Query
1.4.2. 允许放在主体 Dao 的唯一例外
同时满足以下所有条件:
-
JOIN 的表全部属于 同一业务主体
-
关联的是:
- 明细表
- 日志表
- 快照表
-
SQL 不影响其他主体决策
✅ 示例(允许):
java
OrderDao.findOrderWithItems()
-- Order + OrderItem
❌ 示例(禁止):
java
OrderDao.findOrderWithUser()
OrderDao.joinStock()
1.5. 最容易犯错的 3 种情况(必须避免)
1.5.1. 在 Service / Dao 中写跨主体 JOIN
java
UPDATE order o
JOIN stock s ...
1.5.2. 为了"只是查一下",直接跨主体 Dao
java
orderService → userDao
1.5.3. 统计 / 报表复用业务 Dao
java
statisticsService → orderDao
1.6. 快速决策表(写代码前 10 秒)
| 场景 | 放置位置 |
|---|---|
| 单主体业务写 | 对应 Service |
| 多主体业务写 | Facade / Flow Service |
| 多表 JOIN 查询 | Query |
| 日志 / 流水 | 主体 Service |
| 报表 / 统计 | StatisticsService + Query |
1.7. 团队共识(强烈建议贴墙)
不要按"表"拆 Service,要按"业务主体"拆 Service。
不要为了 JOIN 方便,把查询塞进主体 Dao。
2. 标准包结构
下面是一份按真实中大型 Java / Spring 项目整理的,不是教学 Demo。
目标:
- 一眼能看懂分层
- 新人不容易写错
- Code Review 时"看路径就能发现问题"
标准包结构示例(可直接照抄)
示例以 订单 + 用户 + 库存 场景为例
包名你可以换,公司名不重要
java
com.xxx.project
2.1. 整体结构总览(先看全貌)
java
com.xxx.project
├─ controller
├─ application // Facade / 流程编排
├─ domain // 业务主体
│ ├─ order
│ ├─ user
│ └─ stock
├─ query // 只读查询模型(JOIN 主要在这)
├─ statistics // 统计 / 报表
├─ infrastructure // 基础设施(Dao / Mapper / MQ / Cache)
└─ common
2.2. Controller 层(入口)
java
controller
├─ OrderController.java
├─ UserController.java
├─ StockController.java
├─ OrderStockController.java // 对应 Facade
└─ StatisticsController.java
规则
-
Controller 只依赖:
domain.*.serviceapplication.*
-
❌ 不允许出现 Dao / Query
2.3. Application 层(Facade / Flow Service)
所有"跨业务主体写"的逻辑都在这里
java
application
├─ orderstock
│ ├─ OrderStockFacade.java
│ └─ OrderStockFacadeImpl.java
└─ payment
├─ OrderPaymentFacade.java
└─ OrderPaymentFacadeImpl.java
规则
-
只做:
- 调用顺序
- 事务
- 失败处理
-
❌ 不写 SQL
-
❌ 不访问 Dao
2.4. Domain 层(业务主体,最核心)
2.4.1. Order 领域
java
domain/order
├─ service
│ ├─ OrderService.java
│ └─ OrderServiceImpl.java
├─ model
│ ├─ Order.java
│ └─ OrderStatus.java
└─ event
└─ OrderCreatedEvent.java
OrderService 可以:
- 写 OrderDao
- 写 OrderLogDao
- 读 Query
2.4.2. User 领域
java
domain/user
├─ service
│ ├─ UserService.java
│ └─ UserServiceImpl.java
├─ model
│ └─ User.java
└─ validator
└─ UserValidator.java
2.4.3. Stock 领域
java
domain/stock
├─ service
│ ├─ StockService.java
│ └─ StockServiceImpl.java
├─ model
│ └─ Stock.java
└─ policy
└─ StockDeductPolicy.java
2.5. Query 层(只读模型,JOIN 全在这里)
这是架构里最关键的一层
java
query
├─ order
│ ├─ OrderOnlyQuery.java
│ ├─ OrderOnlyQueryImpl.java
│ └─ dto
│ └─ OrderView.java
├─ orderlog
│ ├─ OrderLogOnlyQuery.java
│ └─ OrderLogOnlyQueryImpl.java
├─ user
│ ├─ UserOnlyQuery.java
│ └─ UserOnlyQueryImpl.java
└─ statistics
├─ OrderStatisticsQuery.java
└─ StatisticsView.java
规则
- ✅ 允许 JOIN
- ✅ 允许跨主体
- ❌ 禁止写操作
- ❌ 禁止业务判断
2.6. Statistics 层(横向统计)
java
statistics
├─ StatisticsService.java
└─ StatisticsServiceImpl.java
依赖方向
java
StatisticsService
↓
Query(多个)
❌ 不允许:
- 直接访问 Dao
- 调用 Domain Service
2.7. Infrastructure 层(Dao / Mapper)
java
infrastructure
├─ dao
│ ├─ order
│ │ ├─ OrderDao.java
│ │ └─ OrderLogDao.java
│ ├─ user
│ │ └─ UserDao.java
│ └─ stock
│ └─ StockDao.java
├─ mapper
│ └─ *.xml
├─ cache
│ └─ RedisClient.java
└─ mq
└─ MessageProducer.java
规则
- Dao = 单表
- ❌ 不写 JOIN
- ❌ 不写业务逻辑
2.8. Common 层(通用能力)
java
common
├─ exception
│ └─ BizException.java
├─ util
│ └─ IdGenerator.java
├─ constant
│ └─ ErrorCode.java
└─ base
└─ BaseEntity.java
2.9. 可以直接用的「包级约束口诀」
可以在团队里这样要求:
domain不准依赖applicationquery不准依赖domain.servicecontroller不准依赖daostatistics只准依赖query
2.10. Code Review 一眼判断法(非常好用)
看到一个类,只看路径就能判断对不对:
- 📂
domain/order/service/OrderService
→ 只能有订单逻辑 - 📂
query/order/OrderOnlyQuery
→ 一定是 JOIN + 只读 - 📂
application/orderstock/OrderStockFacade
→ 一定是跨域流程 - 📂
statistics/StatisticsService
→ 一定不写业务
这套包结构 不是最简单的 ,但它是复杂业务下最不容易"写歪"的结构之一。
3. 多模块项目
用 Gradle(尤其是多模块) 做这个分层非常顺手。下面是一套可直接照抄、跑得起来的模板:
web、open都能访问数据库- Entity / Mapper 接口 / mapper.xml 统一复用
- 不放 common ,而是放到
persistence模块(共享数据访问层) - Spring Boot + MyBatis
3.1. 推荐的 Gradle 多模块目录结构
java
project-root
├─ settings.gradle
├─ build.gradle
├─ common
│ └─ build.gradle
├─ persistence
│ ├─ build.gradle
│ └─ src/main
│ ├─ java/com/xxx/persistence
│ │ ├─ entity
│ │ ├─ mapper
│ │ └─ config
│ └─ resources
│ └─ mapper
│ ├─ UserMapper.xml
│ └─ OrderMapper.xml
├─ web
│ └─ build.gradle
├─ open
│ └─ build.gradle
└─ bootstrap
└─ build.gradle
bootstrap是启动模块(可选,但强烈推荐),避免 web/open 同时作为启动入口导致配置乱。
3.2. settings.gradle(必须)
java
rootProject.name = "demo-project"
include(
"common",
"persistence",
"web",
"open",
"bootstrap"
)
3.3. 根 build.gradle(统一版本、插件管理)
java
plugins {
id 'org.springframework.boot' version '3.2.3' apply false
id 'io.spring.dependency-management' version '1.1.4' apply false
id 'java' apply false
}
allprojects {
group = 'com.xxx'
version = '1.0.0'
}
subprojects {
apply plugin: 'java'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
}
3.4. common/build.gradle(纯通用,无数据库依赖)
java
dependencies {
// 放工具类、异常、返回结构等
implementation "org.apache.commons:commons-lang3:3.14.0"
}
common 模块是通用模块。主要放一些其它各模块公用的一些代码:
✅ common 保持"干净",放:
BaseResponse / PageRequest / PageResultBizException / ErrorCode(基础类)工具类 util公共注解通用枚举基类 BaseEnumID生成器常量 GlobalConstants
❌ 不放:
UserDO / OrderDOUserMappermapper.xml数据库依赖与配置
3.5. persistence/build.gradle(共享数据库访问层)
这里放:DO/Entity、Mapper接口、mapper.xml
让 web/open 都
implementation(project(":persistence"))。
java
plugins {
id 'io.spring.dependency-management'
}
dependencies {
implementation project(":common")
// MyBatis Spring Boot Starter
implementation "org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3"
// 数据库驱动(你用什么换什么,这里以 MySQL 为例)
runtimeOnly "com.mysql:mysql-connector-j:8.3.0"
// 可选:Lombok
compileOnly "org.projectlombok:lombok:1.18.30"
annotationProcessor "org.projectlombok:lombok:1.18.30"
}
tasks.withType(Test).configureEach {
useJUnitPlatform()
}
3.5.1. persistence 模块里 MyBatis 扫描与 XML 路径(非常关键)
mapper.xml 放的位置(推荐)
java
persistence/src/main/resources/mapper/*.xml
persistence/config/MybatisConfig.java(可选,但建议)
java
package com.xxx.persistence.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.xxx.persistence.mapper")
public class MybatisConfig {
}
这样就不怕扫描不到 Mapper。
web/open/bootstrap引入 persistence 后会自动生效。
3.5.2. persistence 模块
persistence 是持久层模块。persistence 模块内容建议:
java
persistence
├─ entity // DB 实体(DO / PO)
│ ├─ UserDO.java
│ └─ OrderDO.java
├─ mapper // MyBatis Mapper 接口(Dao)
│ ├─ UserMapper.java
│ └─ OrderMapper.java
├─ xml // mapper.xml(或 resources/mapper)
│ ├─ UserMapper.xml
│ └─ OrderMapper.xml
└─ config // MyBatis 配置 / 扫描配置(可选)
注意:这里的实体最好叫 DO/PO,不要叫 Domain Entity(避免把领域模型和表强绑定)。
Entity / Dao / XML 统一放到 persistence 模块:
示例:
java
persistence/src/main/java/com/xxx/persistence/entity/UserDO.java
persistence/src/main/java/com/xxx/persistence/mapper/UserMapper.java
persistence/src/main/resources/mapper/UserMapper.xml
web/open 直接注入 Mapper 使用:
java
@Autowired
private UserMapper userMapper;
可能会担心的问题:web/open 同时访问 DB,会不会乱?
不会,只要你遵守:
✅ DB 相关东西只在 persistence
✅ 启动配置统一放 bootstrap
✅ web/open 不重复配置数据源
3.6. web/build.gradle(依赖 persistence)
这里建议 web 不直接引数据库驱动、mybatis 依赖,都从 persistence 来。
java
plugins {
id 'org.springframework.boot'
id 'io.spring.dependency-management'
}
dependencies {
implementation project(":common")
implementation project(":persistence")
implementation "org.springframework.boot:spring-boot-starter-web"
implementation "org.springframework.boot:spring-boot-starter-validation"
}
3.7. open/build.gradle(依赖 persistence)
java
plugins {
id 'org.springframework.boot'
id 'io.spring.dependency-management'
}
dependencies {
implementation project(":common")
implementation project(":persistence")
implementation "org.springframework.boot:spring-boot-starter-web"
}
3.8. bootstrap 模块的启动类(Spring Boot main)
java
package com.xxx.bootstrap;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.xxx")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
scanBasePackages="com.xxx"确保扫描到 persistence/web/open 的 bean。
3.8.1. bootstrap/build.gradle(推荐:唯一启动模块)
让 bootstrap 作为唯一入口,web/open 只是功能模块。
java
plugins {
id 'org.springframework.boot'
id 'io.spring.dependency-management'
}
dependencies {
implementation project(":common")
implementation project(":persistence")
implementation project(":web")
implementation project(":open")
implementation "org.springframework.boot:spring-boot-starter"
}
3.8.2. application.yml(放 bootstrap 模块)
bootstrap/src/main/resources/application.yml
java
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=UTC
username: root
password: root
mybatis:
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.xxx.persistence.entity
✅
classpath*:非常重要,保证多模块 resources 能被找到✅ mapper-locations 匹配 persistence/resources/mapper
3.8.3. 如果不设 bootstrap
如果不设 bootstrap,而是 web、open 各自启动:
- web/open 都要有自己的启动类
@SpringBootApplication(scanBasePackages="com.xxx") - application.yml 放各自模块里
缺点:配置容易重复;优点:模块独立运行。