1. 为什么需要「工程结构」?

在单体时代,一个 src/main/java 往往就能跑完整业务;到了微服务,每个服务由 1-3 个小组、5-10 名工程师共同维护,生命周期长达数年。此时「工程结构」不再只是目录,而是一份多人协作“约定由于配置”的契约,它有这些好处:

  • 新人 On-boarding:代码在哪里找?该放哪里?一眼即知。
  • 长期可维护:业务膨胀后仍能快速定位、修改、回滚。
  • 自动化友好:目录即边界,CI、测试、覆盖率、安全扫描都可按模块并行。

2. 模块规范 & Java 类后缀最佳实践

下面是pom依赖关系

模块 职责 类后缀约定
start spring boot启动入口,继承测试放在这个子工程test源码下,所有子工程中的服务都可以在这里写复杂的继承测试 无业务代码,仅启动入口和继承测试代码
gateway 统一接入、路由、鉴权、限流、异常兜底处理,调用biz层,实现api包中的接口能力,消费消息队列的数据 ServiceImpl / Consumer / Handler / Router
api 对外暴露的接口 & 请求/响应模型 DTO / VO / Request / Response / Result / Service / Constants / Enum
biz 围绕业务逻辑,调用core、integration进行业务流程编排、事务、校验,尽量避免biz直接调用dao BizService / BizServiceImpl/ Helper / Executor
integration 调用外部 RPC / 发外域模型的消息 / 依赖模型防腐层,所有DO都需遵循模型最小化,不要把依赖方的模型直接当成自己的模型 Client / ClientImpl / Producer / DO / Request
core 围绕核心领域模型的管理,调用dao进行持久化模型和核心领域模型的转换、组合、同步。比如:一个核心领域模型可能管理多个持久化数据表、同时需要维护持久化和缓存的一致性、还需要把数据的变更下通知给下游应用。 领域模型不需要后缀 / CoreService
dao 数据访问:持久化存储、缓存。和持久化表、缓存kv结构一一映射 DO / Mapper
common 复用常量、枚举、工具、异常 Constants / Enum / Util / Exception
后缀就是 语义化阅读线索。看到OrderBizService就知道是Biz层业务逻辑处理,看到OrderCoreService就知道是订单核心领域模型处理 OrderClient 知道是外部调用;看到 OrderDao 知道是领域存储;看到 OrderMapper 知道是 MyBatis XML 映射。

3. 树形工程目录 & 示例类

下面给出一个典型 order-service 应用的完整目录树。
每个叶子包都提供一个示例类名,可根据需要直接拷贝到 IDE 体验,为了缩短内容,把单测目录省略了,需要的话可自行添加。

order-service
├── start
│   └── src/main/java
│   │   └── com.example.order.starter
│   │       └── Application.java
│   └── src/main/resources
│   │   ├── application.properties
│   │   ├── application-pre.properties
│   │   ├── application-dev.properties
│   │   └── logback.xml
│   └── src/test/java
│   │   └── com.example.order
│   │       └── ApplicationTest.java
│   └── src/test/resources
│       ├── application-test.properties
│       └── logback.xml
├── api
│   └── src/main/java
│       └── com.example.order.api
│           ├── OrderQueryService.java
│           ├── OrderWriteService.java
│           ├── result
│           │   └── BaseResult.java
│           │   └── ListResult.java
│           │   └── PageResult.java
│           ├── request
│           │   └── OrderCreateRequest.java
│           ├── response
│           │   └── OrderCreateResponse.java
│           ├── constants
│           │   └── OrderFeatureConstants.java
│           ├── enums
│           │   └── OrderStatusEnum.java
│           └── dto
│               └── OrderDTO.java
├── gateway
│   └── src/main/java
│       └── com.example.order.gateway
│           ├── impl
│           │   ├── OrderQueryServiceImpl.java
│           │   └── OrderWriteServiceImpl.java
│           ├── filter
│           │   └── AuthFilter.java
│           └── router
│               └── OrderRouter.java
├── biz
│   └── src/main/java
│       └── com.example.order.biz
│           ├── OrderBizService.java
│           ├── impl
│           │   └── OrderBizServiceImpl.java
│           ├── model
│           │   └── OrderBO.java
│           ├── helper
│           │   └── OrderHelper.java
│           ├── executor
│           │   └── PaymentExecutor.java
│           └── validator
│               └── OrderValidator.java
├── integration
│   └── src/main/java
│       └── com.example.order.integration
│           ├── inventory
│           │   ├── InventoryClient.java
│           │   ├── request
│           │   │   └── InventoryRequest.java
│           │   └── model
│           │       └── InventoryDO.java
│           ├── user
│           │   ├── UserQueryClient.java
│           │   ├── request
│           │   │   └── UserQueryRequest.java
│           │   └── model
│           │       └── UserDO.java
│           └── producer
│               └── OrderEventProducer.java
├── core
│   └── src/main/java
│       └── com.example.order.core
│           ├── OrderCoreService.java
│           └── model
│               └── Order.java
├── dao
│   └── src/main/java
│   │   └── com.example.order.dao
│   │       ├── OrderDao.java
│   │       ├── mapper
│   │       │   └── OrderMapper.java
│   │       ├── do
│   │       │   └── OrderDO.java
│   │       └── impl
│   │           └── OrderDaoImpl.java
│   └── src/main/resources
│   │   └── mapper
│   │       └── OrderMapper.xml
└── common
    └── src/main/java
        └── com.example.order.common
            ├── constant
            │   └── BizCodeConstants.java
            ├── enum
            │   └── OrderStatusEnum.java
            ├── util
            │   └── DateUtil.java
            └── exception
                └── BizException.java
说明:
每个子模块(gateway、api…)在真实项目里是 一个 Maven module,不建议用  package来隔离,因为只有maven module才能在编译阶段杜绝不合理的依赖,比如:integration依赖dao就是不合理的
如果团队和业务规模更大,可以把 biz 再拆成 biz-A、biz-B 两个子模块。
测试代码放在同级的 src/test/java,保持镜像结构,只有集成测试才放到start中或者独立一个qatest的模块专门放置所有的集成类。

4. 经验小结

  1. module即边界:严禁跨层调用(如gateway 直接依赖dao )。
  2. 统一命名:各层的类名都有统一的后缀,见类名就能知道它在哪一层、在当前层使用这个类是否合适。
  3. 代码生成:利用 MyBatis Generator / MapStruct 自动生成 DO、Mapper,减少重复劳动。
  4. 演进友好:不一定这个结构完全可以套用到你现在的工程中,取其精华去其糟粕。
  5. 广泛应用:尽量让公司所有java业务系统都采用相同的module结构和命名,那么人员变动、组织调整等的成本会大幅降低。

坚持半年,你会惊喜地发现:

  • 新人一周即可提 PR;
  • 重构只影响一个模块;
  • 线上故障定位从小时级降到分钟级。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐