Skip to content

分层架构与模块拆分

1. 这是什么

分层架构与模块拆分,是把系统按职责和边界组织起来的方法。
它的目标不是为了层数好看,而是为了让系统更清晰、更稳定、更容易演进。

一句话理解:

  • 分层是在回答“不同职责放哪一层”
  • 模块拆分是在回答“不同能力边界怎么分开”

2. 为什么重要

没有合理分层,业务逻辑、数据访问、接口处理会混在一起。
没有合理模块边界,系统一旦扩大就会变得:

  • 难改
  • 难测
  • 难协作

很多“项目越写越乱”的根因,并不是技术栈不高级,而是:

  • 职责边界失控
  • 依赖方向失控

3. 先建立直觉:分层是为了隔离变化,不是为了堆目录

最容易误解的一点是:

  • 看到 controller/service/repository 三层,就以为已经完成了架构设计

其实真正重要的是:

  • 哪类变化应该被隔离在哪一层

例如:

  • HTTP 细节变化,不应该影响核心业务规则
  • 数据库实现变化,不应该直接改动 Controller
  • 一个模块内部变动,不应该轻易传染整个系统

所以分层和拆模块的核心不是形式,而是:

  • 控制变化传播范围

4. 核心内容

4.1 Controller、Service、Repository 的职责

可以先用最经典的后端分层来建立直觉:

Controller

负责:

  • 接收请求
  • 参数校验与转换
  • 返回统一响应

不应该负责:

  • 复杂业务规则
  • 直接拼 SQL

Service

负责:

  • 业务流程编排
  • 业务规则判断
  • 事务边界控制

不应该负责:

  • HTTP 细节
  • 底层数据库访问细节

Repository

负责:

  • 数据访问
  • 对持久化细节做封装

不应该负责:

  • 业务流程编排
  • Web 层响应组装

4.2 模块职责边界

模块拆分最核心的不是“拆多少个”,而是:

  • 一个模块是否围绕稳定职责聚合

更实用的判断方式是:

  • 这个模块是否有清晰边界
  • 这个模块内部变化是否主要由同一类原因触发

4.3 依赖方向控制

一个健康系统常见的依赖方向是:

  • 上层依赖下层
  • 接口层依赖应用层
  • 应用层依赖领域或数据访问层

最危险的是:

  • 反向依赖
  • 横向乱依赖
  • 模块循环依赖

因为一旦依赖方向失控,修改一个点就可能牵动整片代码。

4.4 领域模型与数据模型的区分

很多系统一开始会直接把数据库表结构对象当作整个业务模型。
这样虽然短期快,但中后期容易出问题。

更实用的理解是:

  • 领域模型关注业务语义
  • 数据模型关注存储结构

二者可以重合,但不要默认它们必须完全相同。

4.5 高内聚、低耦合怎么落地

这两个词很常见,但落地时最好转成具体判断:

  • 一个模块内部的类是否围绕同一职责协作
  • 一个模块是否暴露了尽量少、尽量稳定的接口
  • 模块之间是否通过清晰契约交互,而不是互相穿透实现细节

5. 学习重点

这一章最重要的是掌握:

  • 分层是为了隔离变化,不是为了形式完整
  • 模块拆分要围绕职责边界,而不是机械按目录切
  • 依赖方向必须可控
  • Controller、Service、Repository 三层职责要清楚
  • 模块边界一旦混乱,后续演进成本会快速上升

6. 常见问题

6.1 Controller 里写大量业务逻辑

这会让:

  • 接口层承担业务复杂度
  • 测试和复用都变差

6.2 Service 直接拼 SQL 或操作 HTTP 细节

这说明层次职责已经开始混杂。

6.3 模块之间循环依赖

循环依赖会让系统越来越难拆、越来越难替换实现。

7. 动手验证

这一节我用一个最小分层 demo,把“接口层 -> 业务层 -> 数据访问层”的职责边界直接跑出来。

7.1 准备一个可运行示例

新建文件 LayeredArchitectureDemo.java,内容如下:

java
public class LayeredArchitectureDemo {
    static class UserEntity {
        long id;
        String name;

        UserEntity(long id, String name) {
            this.id = id;
            this.name = name;
        }
    }

    interface UserRepository {
        UserEntity findById(long id);
    }

    static class MemoryUserRepository implements UserRepository {
        @Override
        public UserEntity findById(long id) {
            System.out.println("repository-findById");
            return new UserEntity(id, "user-" + id);
        }
    }

    static class UserService {
        private final UserRepository repository;

        UserService(UserRepository repository) {
            this.repository = repository;
        }

        String queryUserName(long id) {
            System.out.println("service-queryUserName");
            UserEntity entity = repository.findById(id);
            return entity.name.toUpperCase();
        }
    }

    static class UserController {
        private final UserService service;

        UserController(UserService service) {
            this.service = service;
        }

        String handleGetUser(long id) {
            System.out.println("controller-handleRequest");
            return "{code:0,data:'" + service.queryUserName(id) + "'}";
        }
    }

    public static void main(String[] args) {
        UserRepository repository = new MemoryUserRepository();
        UserService service = new UserService(repository);
        UserController controller = new UserController(service);

        String response = controller.handleGetUser(1L);
        System.out.println("response=" + response);
    }
}

7.2 编译并运行

bash
javac LayeredArchitectureDemo.java
java LayeredArchitectureDemo

7.3 你应该观察到什么

输出应包含这些关键信息:

text
controller-handleRequest
service-queryUserName
repository-findById
response={code:0,data:'USER-1'}

7.4 每一行在验证什么

  • controller-handleRequest:说明接口层负责接请求和组装响应
  • service-queryUserName:说明业务规则在 Service 层执行
  • repository-findById:说明数据访问细节封装在 Repository 层
  • 最终响应由 Controller 统一组织:说明各层职责是串联而不是混写

8. 练习建议

下面这些练习做完,这一章会更扎实:

  • 拿一个旧项目分析分层是否合理
  • 把一个混乱模块按职责拆分
  • 画一张“模块依赖方向图”
  • 总结模块拆分的判断标准

9. 自测问题

  • 为什么分层不是形式问题而是维护性问题?
  • 模块拆分最核心的依据是什么?
  • 什么样的依赖关系容易导致系统失控?
  • 为什么说分层的核心是隔离变化?

10. 自测核对要点

如果你的回答能覆盖下面这些点,说明这一章基本掌握到位了:

  • 分层是为了职责清晰和变化隔离
  • 模块拆分要围绕稳定职责边界
  • 依赖方向一旦失控,系统维护成本会迅速上升
  • Controller、Service、Repository 各自负责不同层面的工作