3. 项目目录结构与模块划分:分层架构与模块化设计

聊到项目结构,我见过太多新手一上来就把所有代码塞进一个包。刚开始跑得挺欢,等业务逻辑一复杂,那场面……嗯,惨不忍睹。今天咱们就聊聊怎么把架子搭稳。

3.1 分层架构:Controller / Service / DAO

分层架构说白了,就是各司其职。每一层只关心自己的事,不越界。我习惯把这三层比作餐厅:

  • Controller —— 服务员,负责接单、传菜,不负责做菜。
  • Service —— 厨师,负责业务逻辑,怎么搭配食材、火候多大。
  • DAO —— 采购员,负责去仓库拿原材料,不问菜怎么做。

来看一个典型的包结构:

com.yourapp
├── controller
│   └── UserController.java
├── service
│   ├── UserService.java
│   └── impl
│       └── UserServiceImpl.java
├── dao
│   ├── UserDao.java
│   └── impl
│       └── UserDaoImpl.java
├── entity
│   └── User.java
└── config
    └── AppConfig.java

你想想看,如果哪天数据库从 MySQL 换成 PostgreSQL,我只需要改 DAO 层。Controller 和 Service 完全不用动。这就是分层的好处。

核心原则:上层依赖下层,下层不依赖上层。Controller 调用 Service,Service 调用 DAO。反过来不行。

3.2 模块化设计原则

模块化不是把代码拆得越碎越好。我踩过这个坑——曾经把一个工具类拆成十几个小模块,结果找文件比写代码还累。

模块划分有几个硬指标:

原则 说明 反例
高内聚 模块内部功能紧密相关 一个模块既管订单又管物流
低耦合 模块间依赖尽量少 订单模块直接调用支付模块的内部类
单一职责 一个模块只做一件事 用户模块里混入短信发送逻辑
接口隔离 暴露最小接口给外部 一个接口塞了20个方法,调用方只用3个

举个例子。假设我们做一个电商系统,模块可以这样分:

com.yourapp
├── module-order          # 订单模块
│   ├── controller
│   ├── service
│   └── dao
├── module-payment        # 支付模块
│   ├── controller
│   ├── service
│   └── dao
├── module-user           # 用户模块
│   ├── controller
│   ├── service
│   └── dao
└── module-common         # 公共模块(工具类、常量等)
    └── util

我的习惯:每个模块内部也保持三层结构。模块之间通过接口通信,不要直接 new 对方的实现类。这样哪天要拆成微服务,改改配置就行。

3.3 包命名规范与职责划分

包命名这事,看着小,但影响很大。我接手过一个项目,包名全是拼音缩写,光猜意思就花了两天。

业界通用的规范是这样的:

  • 域名倒置:com.公司名.项目名.模块名
  • 全小写:不要驼峰,不要下划线
  • 按功能分层:controller、service、dao、entity、config
  • 按模块分包:module-order、module-payment

来看一个完整的例子:

com.yourapp
├── controller
│   ├── OrderController.java
│   └── PaymentController.java
├── service
│   ├── OrderService.java
│   └── impl
│       └── OrderServiceImpl.java
├── dao
│   ├── OrderDao.java
│   └── impl
│       └── OrderDaoImpl.java
├── entity
│   ├── Order.java
│   └── OrderItem.java
├── dto
│   ├── OrderCreateRequest.java
│   └── OrderVO.java
├── config
│   ├── DataSourceConfig.java
│   └── MyBatisConfig.java
├── enums
│   └── OrderStatus.java
├── exception
│   └── OrderNotFoundException.java
└── util
    └── OrderNumberGenerator.java

每个包的职责很清晰:

包名 职责 典型内容
controller 接收请求、返回响应 @RestController、参数校验
service 业务逻辑编排 事务控制、规则判断
dao 数据访问 SQL、ORM 映射
entity 数据模型 数据库表映射对象
dto 数据传输对象 请求/响应封装
config 配置类 数据源、拦截器、过滤器
enums 枚举常量 状态、类型、错误码
exception 自定义异常 业务异常、系统异常
util 工具类 日期处理、ID生成

避坑指南:我曾经把工具类放在 entity 包里,结果 entity 包越来越大,最后连数据库字段和工具方法混在一起。后来重构时花了整整一周。记住:工具类永远放在 util 包,不要往 entity 里塞。

3.4 分层架构的核心逻辑图

下面这张图展示了三层之间的调用关系和数据流向。我建议你把它存下来,写代码前看一眼。

分层架构核心调用关系 Controller 层 接收请求 · 参数校验 · 返回响应 调用 Service 接口 Service 层 业务逻辑 · 事务控制 · 规则编排 调用 DAO 接口 DAO 层 数据访问 · SQL 执行 · ORM 映射 读写数据库 数据库 关键约束 • 上层依赖下层 • 下层不依赖上层 • 同层可横向调用 • 接口隔离原则

3.5 实际项目中的常见问题

说了这么多理论,聊聊我实际遇到的坑。

问题一:Controller 里写业务逻辑

我见过有人直接在 Controller 里写 SQL 拼接。结果呢?单元测试没法写,代码没法复用,改个逻辑要改三个地方。记住:Controller 只做两件事——接参数、调 Service、返回结果。

问题二:Service 层太胖

Service 层不是垃圾桶。我曾经把一个模块的 Service 写了 2000 行,里面既有订单逻辑又有支付逻辑。后来拆成三个 Service,每个不到 300 行,清爽多了。

问题三:DAO 层返回 Map

有些人图省事,DAO 层直接返回 Map<String, Object>。短期看是快了,长期看就是灾难。调用方不知道 Map 里有什么 key,改个字段名全盘崩。老老实实用实体类或者 DTO。

总结一下:分层架构不是银弹,但它能帮你把代码组织得井井有条。模块化设计也不是越细越好,找到合适的粒度才是关键。包命名规范看着是小事,但团队协作时能省下大量沟通成本。