本文介绍在 spring boot 中如何合理划分 service 层职责,通过工厂+策略模式将“数据解析→查询构建→数据库执行→结果封装”四步流程解耦,避免 service 膨胀,提升可测试性与可扩展性。
本文介绍在 spring boot 中如何合理划分 service 层职责,通过工厂+策略模式将“数据解析→查询构建→数据库执行→结果封装”四步流程解耦,避免 service 膨胀,提升可测试性与可扩展性。
在构建高内聚、低耦合的业务服务时,一个常见的反模式是将所有逻辑(如参数解析、条件组装、DAO 调用、DTO 转换)全部堆砌在单一 Service 方法中。尤其在面向多类型查询(如 RockQuery、PopQuery、FolkQuery)的场景下,这种设计会迅速导致 SongService 变得臃肿、难以维护且无法单元测试。理想的方案是让 Service 仅承担协调者(Orchestrator)角色,而将具体行为委托给职责明确、可替换的策略组件。
✅ 推荐架构:职责分离 + 工厂驱动 + 不可变数据流
核心原则是:数据(如 List
以下为推荐实现结构(已适配 Spring Boot 最佳实践):
1. 定义策略接口(无状态、纯行为)
// 行为契约:解析输入、构建查询条件(非 SQL 字符串,而是类型安全的 QueryBuilder)
public interface Query {
// 将原始输入(如 Data 对象)解析为领域参数(如 genre, decade, bpmRange)
QueryParams parse(Object input);
// 基于解析结果,生成 Repository 可识别的查询构建器(如 Example, Specification, 或自定义 Criteria)
QueryBuilder createQueryBuilder(QueryParams params);
}⚠️ 注意: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 Optional.ofNullable(queryStrategies.get(type.toUpperCase()))
.orElseThrow(() -> new IllegalArgumentException("Unsupported query type: " + type));
}
}3. Service 层:轻量协调,显式流转
@Service
public class SongService {
private final SongRepository songRepository;
private final QueryFactory queryFactory;
private final PrepareAnswerFactory prepareAnswerFactory; // 同理,按类型返回 SongDTO 转换器
public SongService(SongRepository songRepository,
QueryFactory queryFactory,
PrepareAnswerFactory prepareAnswerFactory) {
this.songRepository = songRepository;
this.queryFactory = queryFactory;
this.prepareAnswerFactory = prepareAnswerFactory;
}
public List<SongDTO> getSongsByType(String type, Object inputData) {
// Step 1: 获取对应策略
Query queryStrategy = queryFactory.createQuery(type);
// Step 2: 解析输入 → 构建查询条件
QueryParams params = queryStrategy.parse(inputData);
QueryBuilder builder = queryStrategy.createQueryBuilder(params);
// Step 3: 执行查询(Repository 负责具体实现,Service 不感知 JPA/MyBatis 细节)
List<SongItem> rawSongs = songRepository.findByBuilder(builder);
// Step 4: 委托转换器生成 DTO(支持不同视图:精简版/详情版/统计版)
PrepareAnswer converter = prepareAnswerFactory.createFor(type);
return converter.prepareAnswer(rawSongs); // 输入 rawSongs,输出 List<SongDTO>
}
}4. 关键优势与注意事项
-
✅ 纯函数式流转:List
仅作为方法参数存在,生命周期清晰,无共享状态,天然线程安全; - ✅ 单元测试友好:每个 Query 实现可独立 Mock parse() 和 createQueryBuilder();PrepareAnswer 可用真实数据验证转换逻辑;Service 层只需验证调用顺序与参数传递;
- ✅ 开闭原则落地:新增查询类型(如 JazzQuery)只需实现 Query、注册到 QueryFactory,无需修改 SongService;
-
⚠️ 避免陷阱:
- ❌ 不要在 Query 实现类中注入 SongRepository 并执行查询(违反单一职责,且破坏策略的可复用性);
- ❌ 不要将 List
设为 Query 的成员变量(引入隐式状态,导致实例无法复用、测试困难); - ✅ Repository 接口应抽象 findByBuilder(QueryBuilder),而非暴露 JPA Example 或 MyBatis Criteria —— 保持上层对持久层技术的隔离。
总结
真正的“高内聚”,不是把所有代码塞进一个类,而是让每个类只做一件事,并清晰声明它需要什么、产出什么。在本例中,SongService 的唯一职责是编排流程,Query 负责理解业务意图并翻译为查询指令,PrepareAnswer 负责将领域模型映射为 API 契约。三者通过接口协作,数据以参数形式显式流动——这既是 Spring Boot 分层架构的精髓,也是可演进微服务系统的设计基石。










