本文介绍如何在 spring boot 项目中合理拆分 service 层职责,通过工厂模式、策略接口与清晰的数据流转,将“解析→查询→转换”流程解耦为高内聚、低耦合的组件,避免 service 类膨胀并提升可测试性与可维护性。
本文介绍如何在 spring boot 项目中合理拆分 service 层职责,通过工厂模式、策略接口与清晰的数据流转,将“解析→查询→转换”流程解耦为高内聚、低耦合的组件,避免 service 类膨胀并提升可测试性与可维护性。
在构建领域逻辑复杂的业务服务(如多类型音乐查询系统)时,一个常见的反模式是将所有步骤——数据解析、SQL 构建、数据库调用、DTO 封装——全部堆砌在单个 @Service 类中。这不仅导致类职责模糊、难以单元测试,更阻碍了按查询类型(如 Rock、Pop、Folk)灵活扩展。理想的解决方案是以“流程协调者”定位 Service,将具体行为委托给专注单一职责的策略组件。
✅ 推荐架构:职责分离 + 工厂驱动 + 不可变数据流
核心原则是:Service 不持有中间状态,不实现业务逻辑,只负责编排;数据(如 List
以下是经过生产验证的结构设计:
1. 定义策略接口(面向行为,而非状态)
// 行为契约:每个 Query 实现负责自身类型的解析与查询构建
public interface Query {
// 解析原始输入(如 JSON/Map),返回查询条件对象(如 SongCriteria)
SongCriteria parse(Object rawData);
// 基于条件构建类型安全的查询(如 JPA CriteriaBuilder 或自定义 QueryBuilder)
QueryBuilder createQueryBuilder(SongCriteria criteria);
}⚠️ 注意:parse() 和 createQueryBuilder() 均为无状态操作,不依赖或修改内部字段。避免将 List
设为类成员——这会引入隐藏状态、破坏策略的幂等性与并发安全性。
2. 使用工厂解耦实例创建
@Component
public class QueryFactory {
private final Map<String, Query> queryStrategies;
public QueryFactory(RockQuery rockQuery, PopQuery popQuery, FolkQuery folkQuery) {
this.queryStrategies = Map.of(
"ROCK", rockQuery,
"POP", popQuery,
"FOLK", folkQuery
);
}
public Query createQuery(String type) {
return queryStrategies.getOrDefault(type.toUpperCase(),
throw new IllegalArgumentException("Unsupported query type: " + type));
}
}同理,PrepareAnswer 的工厂化封装 DTO 转换逻辑:
public interface PrepareAnswer {
List<SongDTO> prepareAnswer(List<SongItem> items);
}
@Component
public class PrepareAnswerFactory {
private final Map<String, PrepareAnswer> converters;
public PrepareAnswerFactory(RockAnswerConverter rockConv, PopAnswerConverter popConv) {
this.converters = Map.of("ROCK", rockConv, "POP", popConv);
}
public PrepareAnswer createFor(String type) {
return converters.getOrDefault(type.toUpperCase(),
(items) -> items.stream().map(SongDTO::from).toList());
}
}3. Service 层:纯粹的协调中枢
@Service
public class SongService {
private final SongRepository songRepository;
private final QueryFactory queryFactory;
private final PrepareAnswerFactory prepareAnswerFactory;
// 构造器注入,明确依赖,便于测试
public SongService(SongRepository songRepository,
QueryFactory queryFactory,
PrepareAnswerFactory prepareAnswerFactory) {
this.songRepository = songRepository;
this.queryFactory = queryFactory;
this.prepareAnswerFactory = prepareAnswerFactory;
}
public List<SongDTO> getSongsByType(String queryType, Object inputData) {
// Step 1: 获取对应策略
Query queryStrategy = queryFactory.createQuery(queryType);
// Step 2: 解析输入 → 构建查询条件 → 执行查询(数据流清晰可见)
SongCriteria criteria = queryStrategy.parse(inputData);
QueryBuilder builder = queryStrategy.createQueryBuilder(criteria);
List<SongItem> rawSongs = songRepository.findByQueryBuilder(builder);
// Step 3: 委托转换,不侵入业务逻辑
PrepareAnswer converter = prepareAnswerFactory.createFor(queryType);
return converter.prepareAnswer(rawSongs);
}
}? 关键设计决策解析
| 决策点 | 为什么这样选? | 风险规避 |
|---|---|---|
| 数据不存于策略对象中 | 保持策略无状态、可复用、线程安全;避免 RockQuery 实例被误用于 Pop 场景导致数据污染 | 防止因共享状态引发的并发 bug 和测试干扰 |
| Service 不直接调用 repository.query(…) | 将查询执行权保留在 Repository 层,符合 Spring Data 分层规范;QueryBuilder 封装了 JPA/Hibernate 细节,Service 无需感知 | 避免 Service 变成“胶水代码”,降低与 ORM 的耦合度 |
| 工厂类管理策略映射 | 新增查询类型只需添加实现类 + 注册到工厂,无需修改 Service 或 if-else 分支 | 符合开闭原则(OCP),支持运行时动态扩展(如结合配置中心) |
? 总结:让 Service 回归本职
一个健康的 Spring Boot Service 应当是:
- 轻量的:无业务逻辑,仅协调;
-
确定的:输入(inputData, queryType)→ 输出(List
),无隐藏状态; - 可组合的:策略组件可自由替换、装饰(如添加缓存拦截器)、Mock 测试;
- 可演进的:未来若需增加“查询审计日志”,只需在 getSongsByType 中插入一行 auditLogger.log(...),无需动任何策略类。
遵循此模式,你的 SongService 将不再是“万能上帝类”,而是一条清晰、健壮、易于监控与调试的业务流水线。










