为什么 Service 层不能只写 CRUD——后端业务边界的真正意义
摘要:Service层不应仅是CRUD包装,其核心价值在于表达业务用例和系统流程。健康Service层应具备四大职责:业务流程编排、事务边界控制、业务建模和系统协作入口。CRUD型Service会导致业务逻辑分散、事务混乱和架构难以演进。正确做法是将CRUD下沉至Repository,让Service专注于业务逻辑编排(如registerUser、createOrder等),而业务规则应放在Dom
很多后端项目,到一定阶段都会变成这样:
Controller 很薄
Repository 很多
Service 层全是:
save()、update()、getById()、list()看起来分层齐全,实际上整个系统在结构上已经塌了一半。
因为:
👉 Service 层如果只剩 CRUD,这个系统本质上没有“业务层”。
这篇文章,专门讲一个分水岭问题:
👉 Service 层到底存在的意义是什么?为什么它绝对不能只写 CRUD?
一、先给结论:CRUD 型 Service = 伪分层
如果一个项目里:
UserService.save(user)
UserService.update(user)
UserService.getById(id)
Service 层只是把 Repository 包了一层。
那么这个项目,本质结构是:
Controller → DAO → Database
👉 Service 层只是“形式存在”,不是“架构存在”。
这种系统通常具备几个特征:
- 业务规则散落在 Controller / SQL / 各种 if else
- 事务到处飞
- 改一个规则,全链路跟着动
- 项目越大,越不敢动
👉 这是绝大多数“中后期失控系统”的起点。
二、Service 层存在的真正原因
Service 层不是为了“多一层”。
它的真实使命只有一个:
👉 表达“业务用例”和“系统流程”。
也就是说:
- 一个用户注册
- 一个订单创建
- 一次支付流程
- 一次库存扣减
- 一次状态流转
这些,都应该是 Service 层的语言。
Service 层的核心关键词是:
👉 编排(Orchestration)
而不是:
👉 存取(CRUD)
三、CRUD 型 Service 和业务型 Service 的本质区别
❌ CRUD 型 Service(伪业务层)
public void save(User user){
userRepository.save(user);
}
特征:
- 一方法一张表
- 没有业务语义
- 没有流程概念
- 没有边界
👉 它本质是 DAO 的“别名”。
✅ 业务型 Service(真实业务层)
@Transactional
public void register(RegisterUserCommand cmd){
User user = User.create(cmd);
userRepository.save(user);
Account account = Account.init(user.getId());
accountRepository.save(account);
couponService.initFor(user);
eventPublisher.publish(new UserRegisterEvent(user));
}
特征:
- 一个方法 = 一个业务动作
- 多实体协作
- 有事务边界
- 有失败语义
- 可演进
👉 这才是后端系统真正的“骨架”。
四、Service 层的四个核心职责
一个健康的 Service 层,至少承担四类责任:
1️⃣ 业务流程编排
Service 层关心的是:
👉 “一件事如何在系统中完成”
而不是:
👉 “一条数据怎么存”。
例如:
-
创建订单 = 校验 → 生成订单 → 锁库存 → 写明细 → 发消息
👉 这是流程,不是 CRUD。
2️⃣ 事务边界控制
事务几乎永远应该收敛在 Service 层。
因为:
👉 事务表达的是业务一致性边界。
- 哪些操作必须一起成功
- 哪些失败要整体回滚
这些,只有业务层知道。
3️⃣ 业务边界与用例建模
Service 层的方法名,本质是在回答:
👉 系统“能干什么”。
例如:
- registerUser
- createOrder
- payOrder
- cancelOrder
- transferMoney
👉 它们是“业务语言”,不是“数据语言”。
4️⃣ 系统协作入口
Service 层是:
- Controller 调用的入口
- 定时任务调用的入口
- MQ 消费调用的入口
- RPC 接口调用的入口
👉 它是整个系统对外暴露的“能力边界”。
五、为什么 CRUD 型 Service 一定会失控?
因为它天然会导致三件事:
❌ 1. 业务逻辑四散
- Controller 写一点
- SQL 写一点
- 定时任务再写一点
👉 系统没有“业务核心”。
❌ 2. 事务边界混乱
- 有的在 Controller
- 有的在 DAO
- 有的根本没有
👉 数据一致性靠运气。
❌ 3. 架构无法演进
一旦出现:
- 分库分表
- 缓存
- 事件驱动
- 微服务拆分
CRUD Service 会全盘爆炸。
六、如何判断你的 Service 层是不是“真业务层”?
给你一个非常实用的自检表:
✅ 好 Service 的特征
- 方法名是业务动词,不是数据库动词
- 一个方法经常操作多张表
- Service 层最容易写事务
- Service 可以被多入口复用
- Service 里经常出现领域对象行为
❌ 坏 Service 的特征
- 满屏 save/update/get
- 一个方法只调一个 DAO
- Service 看起来像“转发器”
- 业务规则散落各处
七、那 CRUD 应该放哪?
结论很简单:
👉 CRUD 是基础设施能力。
它的自然归宿是:
- Repository
- Mapper
- DAO
- Persistence
Service 层可以用 CRUD,但不应该“等于 CRUD”。
八、Service 层与 Domain 层的关系
很多人会再问一句:
👉 那业务逻辑到底放 Service 还是 Domain?
一句工程级判断:
- 业务流程 → Service
- 业务规则 → Domain
例如:
- “注册要创建用户 + 初始化账户” → Service
- “用户手机号必须合法” → Domain
👉 Service 负责“调度”,Domain 负责“成立”。
九、总结一句非常重要的话
👉 Repository 决定你怎么“存数据”。
👉 Domain 决定你“业务是否成立”。
👉 Service 决定你“系统能干什么”。
如果 Service 退化为 CRUD:
👉 这个系统,本质就退化成了“数据库脚本集合”。
十、阶段性总结
- Service 层不是 DAO 包装层
- Service 层是业务能力层
- Service 层是事务边界层
- Service 层是系统演进支点
当你开始自然地写出:
createOrder()cancelOrder()payOrder()
而不是:
saveOrder()updateOrder()
你就已经从“写接口”,走向了“建系统”。
更多推荐


所有评论(0)