企业级开发中,Controller 层更多扮演"流量入口",DAO 层负责与数据库对话,而真正承载业务逻辑、规则聚合、流程协调的核心层------是 Service 层。
Service 层写得好,系统结构清晰、扩展性强、维护成本低。 Service 层写得乱,你会看到:
-
事务乱用,脏数据频发
-
Service 方法几千行,变成"业务垃圾桶"
-
幂等没有处理,重复订单、重复扣费
-
Controller 业务化、Service 空壳化
-
复杂业务写成一坨,根本无法复用
-
新人看不懂老代码,维护成本指数上升
1、Service 层到底应该做什么?(三大职责)
很多人写 Service 的第一天,就已经写歪了。
真正标准的 Service 层职责有三点:
-
业务流程编排(Process Orchestration) 串联多个步骤、模块、服务,让业务以正确顺序执行。
-
业务规则聚合(Business Logic) 校验业务规则,如"库存是否足够""用户是否可下单"。
-
事务边界控制(Transaction Boundary) 负责 "这段操作必须原子化执行"。
一句话总结:
Service 层负责------对外承接需求,对内协调流程。
它不应该做:
- 复杂 SQL(应由 Mapper 负责)
- 直接操作缓存(应在 Manager 层)
- 维护业务状态(应由 Domain 层)
- 操作文件、第三方请求(应由 Infrastructure 层)
2、Service 层最容易写错的几件事(也是你要避开的雷区)
① Controller 写业务、Service 写 CRUD
这是最常见的反模式:
java
@PostMapping("/order")
public OrderVO createOrder(@RequestBody OrderDTO dto) {
// 业务校验、扣库存、保存订单、发消息都写在 Controller......
}
问题:
- Controller 没有事务
- 业务分散、无法复用
- 修改极其困难
② Service 成了"上帝类",几千行
直接造成:
- 新人无法维护
- 无法测试
- 功能耦合严重
③ 不加事务或滥用事务
例如:
java
@Transactional
public void createOrder() {
deductStock();
saveOrder();
sendMQ(); // 这里不应该在事务里
}
外部系统调用(MQ、短信、HTTP)一旦放进事务,极易导致数据不一致。
④ 幂等没处理
高并发时产生:
- 重复扣库存
- 重复下单
- 重复转账
- 重复计算积分
3、Service 层的正确结构:三层模型

企业级系统常用的业务分层如下:
解释一下:
① Application Service(应用服务层)
负责业务流程编排:
- 入参校验(增强)
- 组合多个 Domain Service 的功能
- 控制事务边界
类似:
java
@Transactional
public Order createOrder(CreateOrderDTO dto) {
userDomain.checkUserStatus(dto.getUserId());
productDomain.checkStock(dto.getProductId());
Order order = orderDomain.create(dto);
orderDomain.notify(order);
return order;
}
② Domain Service(领域服务层)
负责业务核心规则:
- 校验复杂业务规则
- 维护领域对象状态
- Domain 内部逻辑
例如:
java
public void checkStock(Long productId) {
Product product = productManager.get(productId);
if (product.getStock() <= 0) {
throw new BizException("库存不足");
}
}
③ Manager(资源访问层)
负责:
- DB
- Redis
- HTTP(第三方)
- MQ
- OSS
示例:
java
public class ProductManager {
public Product get(Long id) {
return productMapper.selectById(id);
}
}
Manager 就是 集中的资源访问入口。
4、Service 层的事务设计:如何真正防止脏数据?
事务的终极规则只有一条:
事务应该包住"改变系统状态的一整套操作"。
也就是说:
- 查询不要写事务
- 调用 MQ / 外部接口不要放事务内
- 循环写 DB 时要分段控制
- 跨 Service 的事务最好回归 Application Service 来管
事务的最佳写法
推荐把事务写在 Application Service:
java
@Transactional(rollbackFor = Exception.class)
public Long createOrder(OrderDTO dto) {
productDomain.checkStock(dto.getProductId());
orderDomain.saveOrder(dto);
productDomain.reduceStock(dto.getProductId());
// 业务完成后再发消息,而不是写在事务里
eventPublisher.publishOrderCreated(dto);
return dto.getId();
}
哪些操作一定不能写在事务里?
- Redis 写入
- MQ 发送
- HTTP 调用
- OSS 上传
- 文件写入
- 非数据库型资源访问
理由非常简单:
事务失败不会回滚这些操作,会导致严重不一致。
4、Service 层的幂等性设计:如何避免重复执行?
幂等(Idempotency)的核心思想是:
同一请求执行 1 次与执行 N 次,结果必须一致。
常见场景:
- 创建订单不能重复
- 扣费不能重复
- 创建支付单必须唯一
- 积分发放不能重复
- 消息重复消费
企业级幂等的三种方案
① 幂等 Token(推荐用于前端请求)
流程:
- 前端请求生成 token
- 后端缓存 token → Redis SETNX
- 调用业务时携带 token
- 缓存中 token 删除后,不允许再次使用
示例代码:
java
public boolean checkIdempotent(String key) {
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
if (!Boolean.TRUE.equals(success)) {
throw new BizException("请勿重复提交");
}
return true;
}
② 去重表(用于业务唯一性)
订单:
java
insert into order(id, sn, user_id) values(?,?,?)
-- sn 做唯一索引
违反唯一约束 = 重复提交。
③ 状态机幂等(用于支付、物流、状态流转)
例如支付状态:

如果当前状态是 SUCCESS,再执行扣费,必须拒绝。
6、业务的拆分与聚合:如何避免"上帝 Service"?
Service 之所以被写成几千行,是因为不会拆。
给你一套「企业级拆分规则」:
① 按业务流程拆分,而不是按 CRUD 拆分
不要写:
UserService
OrderService
而是写:
- UserDomainService
- UserProfileService
- UserPointService
- OrderCreateService
- OrderCancelService
- OrderRefundService
业务清晰很多。
② 复杂流程抽象成独立方法,不要写成一坨
错误写法:
java
public void createOrder() {
// 校验用户
// 校验库存
// 创建订单
// 扣库存
// 发消息
}
正确写法:
java
public void createOrder() {
validateUser();
validateStock();
Order order = generateOrder();
reduceStock();
postEvent(order);
}
好处:
- 可阅读
- 可复用
- 单元测试更简单
- 新人更容易理解
③ 方法名必须体现"业务含义",而不是"技术动作"
错误:
- save()
- update()
- handler()
- process()
正确:
- checkUserStatus()
- validateProduct()
- generateOrder()
- ockStock()
- publishOrderEvent()
可读性提升一个量级。
7、业务抽象:如何让 Service 层可扩展、可维护?
业务抽象的核心目的是:
变化的隔离 & 稳定部分共享。
① 提取共性逻辑:抽象成 Base Domain
例如支付抽象:
java
public abstract class PayService {
public final PayResult pay(PayRequest request) {
validate(request);
doPay(request);
return afterPay(request);
}
protected abstract void validate(PayRequest request);
protected abstract void doPay(PayRequest request);
protected abstract PayResult afterPay(PayRequest request);
}
支付宝、微信都继承它。
② 横切逻辑抽象:如幂等/日志/权限
例如幂等:
java
@Around("@annotation(Idempotent)")
public Object idempotent(ProceedingJoinPoint pjp) {
String key = buildKey(pjp);
if (!tryAcquire(key)) {
throw new BizException("请勿重复操作");
}
return pjp.proceed();
}
不污染业务代码。
③ 复杂业务状态抽象为领域对象(DDD)
例如订单:
java
public class Order {
private OrderStatus status;
public void pay() {
if (status != OrderStatus.CREATED) {
throw new BizException("状态非法");
}
status = OrderStatus.PAID;
}
}
业务规则放在对象内部,Service 更轻。
8、一个综合示例:从 Controller 到 Service 到 Domain
完整链路示例:
POST /order/create
Controller
java
@PostMapping("/create")
public Long createOrder(@RequestBody CreateOrderDTO dto) {
return orderApplicationService.createOrder(dto);
}
ApplicationService(事务层)
dart
@Service
publicclass OrderApplicationService {
@Transactional
public Long createOrder(CreateOrderDTO dto) {
userDomain.checkUserStatus(dto.getUserId());
productDomain.checkStock(dto.getProductId());
Order order = orderDomain.create(dto);
productDomain.reduceStock(dto.getProductId());
orderEventPublisher.publishOrderCreated(order);
return order.getId();
}
}
DomainService
dart
public class OrderDomainService {
public Order create(CreateOrderDTO dto) {
return Order.create(dto);
}
}
Manager
dart
public class OrderManager {
public void save(Order order) {
orderMapper.insert(order);
}
}
整个流程清晰、职责明确、结构可控。
总结
Service 层是整个后端的"中枢神经",写好它,你的系统会有以下变化:
- 事务边界清晰
- 业务逻辑易理解、易维护
- 幂等可控,不怕高并发
- Controller 轻,Manager 纯
- 复杂业务结构自然、可测试、可拆分
- 系统稳定性与扩展性大幅提升