最近做 Code Review,看到一个聪明的新同事,用策略模式、工厂模式、抽象工厂,写了一个极其复杂的订单处理系统。这个系统本来只需要处理两种支付方式,现在却被设计成了可以“轻松扩展”到20种支付方式的架构。
典型的场景:用户下单,根据支付类型调用不同的支付渠道,代码大概长这样:
// 策略模式接口
interface PaymentStrategy {
PayResult pay(Order order);
}
// 工厂模式
class PaymentStrategyFactory {
private Map<String, PaymentStrategy> strategies = new HashMap<>();
public PaymentStrategyFactory() {
strategies.put("alipay", new AlipayStrategy());
strategies.put("wechat", new WechatPayStrategy());
// ... 理论上可以无限扩展
}
public PaymentStrategy getStrategy(String type) {
return strategies.get(type);
}
}
// 抽象工厂
interface PaymentFactory {
Validator createValidator();
Notifier createNotifier();
Logger createLogger();
}
// 具体使用
public class OrderService {
private PaymentStrategyFactory strategyFactory;
private PaymentFactory paymentFactory;
public PayResult processOrder(Order order) {
PaymentStrategy strategy = strategyFactory.getStrategy(order.getPayType());
PaymentFactory factory = getFactory(order.getPayType());
factory.createValidator().validate(order);
PayResult result = strategy.pay(order);
factory.createNotifier().notify(result);
factory.createLogger().log(result);
return result;
}
}
我把这哥们叫过来,问他:“咱们系统现在就支付宝和微信支付两种,为什么不用简单的if-else,或者用枚举+策略模式简化一下?”
他说:“这样设计扩展性好啊,以后加新支付方式特别方便,代码也显得专业😎。”
这个瞬间,让我想聊聊这个话题:在现代后端开发(尤其是微服务时代)中,我们挂在嘴边的那些经典架构模式,90%都是在过度设计。
先澄清一下:我不反对好的设计思想,比如高内聚低耦合、单一职责。我反对的是,把那些为大型单体应用设计的、沉重的、过度抽象的架构模式,生搬硬套到我们现代的微服务、云原生、Serverless的架构里。很多时候,我们需要的是回归简单的实践,而不是理论的堆砌。如果你也对这个话题有共鸣,欢迎来云栈社区聊聊,那里有更多一线开发者分享他们的实战经验和避坑指南。
我们为什么会陷入架构模式的陷阱?
曾经,我也是《设计模式》、《企业应用架构模式》的忠实读者。热衷于在代码里寻找应用Repository模式、CQRS、六边形架构的机会。
我觉得原因有两个:
1. 为了面试造火箭
架构模式是后端面试的重灾区。面试官喜欢问:“你怎么设计一个高并发的系统?”我们为了应对这种问题,不得不背诵各种架构模式的名字和用途,导致工作中也总想“秀一下肌肉”。
2. 技术虚荣心作祟
我们总觉得,能说出几个架构模式的名字,能在代码里用上DDD、事件溯源、CQRS,就代表自己水平更高。仿佛不说个“领域驱动设计”,不提个“最终一致性”,就显得不够资深。
有哪些水土不服的架构模式?
1. 抽象工厂模式(Abstract Factory)
经典用法:为创建相关或依赖对象提供一个接口,而无需指定具体类。
我的吐槽:在微服务架构里,每个服务应该有自己的数据库和业务逻辑。跨服务的对象创建?你确定不是在造分布式单体?
现代做法:每个微服务维护自己的数据模型和工厂逻辑。如果需要跨服务的数据,用API调用或者事件通信,而不是抽象工厂。
// 别搞这么复杂的抽象工厂了
// 微服务A的代码
@Service
public class OrderService {
// 直接依赖具体的仓储或客户端
@Autowired
private PaymentClient paymentClient;
// 简单清晰
public Order createOrder(CreateOrderRequest request) {
Order order = new Order();
order.setItems(request.getItems());
order.setTotal(request.getTotal());
// ... 业务逻辑
return orderRepository.save(order);
}
}
2. 复杂的事件溯源(Event Sourcing)
经典用法:不存储对象当前状态,而是存储导致状态变化的所有事件。
我的吐槽:兄弟,咱们就是个电商订单系统,不是银行核心系统!你真的需要完整的历史记录来重建每个对象的状态吗?大部分业务,在数据库里加个 version 字段和审计日志就够了。
现代做法:按需使用。只有真正需要完整审计追踪、法律合规要求的场景,才考虑事件溯源。
// 大部分情况下,这样就够了
@Entity
public class Order {
@Id
private Long id;
private String status;
private BigDecimal amount;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
// 简单的状态变更
public void cancel() {
this.status = "CANCELLED";
this.updatedAt = LocalDateTime.now();
}
}
3. CQRS(命令查询职责分离)的滥用
经典用法:把读写模型分开,用不同数据库甚至不同服务处理。
我的吐槽:90%的业务场景,读写比例是9:1甚至99:1。为了那1%的写操作,你把整个架构搞得这么复杂?读写分离数据库+缓存,通常就够了。
现代做法:从简单开始,真的遇到性能瓶颈再考虑CQRS。
// 开始的时候,简单点
@RestController
public class OrderController {
// 读:走缓存
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
return cacheService.getOrLoad(
"order:" + id,
() -> orderRepository.findById(id).orElseThrow()
);
}
// 写:直接写库
@PostMapping("/orders")
public Order createOrder(@RequestBody CreateOrderRequest request) {
Order order = orderService.createOrder(request);
// 清理缓存
cacheService.evict("order:" + order.getId());
return order;
}
}
4. 过度设计的领域驱动设计(DDD)
经典用法:聚合根、实体、值对象、领域服务、仓储接口、防腐层...
我的吐槽:咱们就是一个用户管理模块,用户有基本信息、地址、权限。真的需要搞出 UserAggregateRoot、AddressValueObject、UserRepositoryInterface、UserDomainService 这一堆东西吗?
现代做法:用DDD的思想,而不是形式。关注领域逻辑,而不是分层数量。
// 简单清晰的领域模型
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
private Long id;
private String username;
private String email;
@Embedded
private Address address; // 值对象,但不用非得叫ValueObject
// 领域逻辑放在实体里
public boolean canResetPassword() {
return !isLocked() && isActive();
}
public void changeEmail(String newEmail) {
validateEmail(newEmail);
this.email = newEmail;
this.emailVerified = false;
// 发送验证邮件的逻辑可以放这里或服务层
}
}
那剩下10%有用的,是什么?
我喷了90%,那剩下10%依然有价值的是什么?是一些架构思想,而不是具体的实现形式。
1. 清晰的依赖方向
有用之处:高层模块不应该依赖低层模块,两者都应该依赖抽象。
现代实践:在Spring Boot里,用接口定义Service,用实现类实现。但别为了抽象而抽象。
// 有用的抽象:定义清晰的契约
public interface PaymentService {
PaymentResult pay(PaymentRequest request);
RefundResult refund(RefundRequest request);
}
@Service
public class AlipayService implements PaymentService {
// 具体实现
}
// 使用时依赖接口
@Service
public class OrderService {
private final PaymentService paymentService; // 依赖抽象
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
2. 策略模式的精简版
有用之处:消除if-else,让代码更清晰。
现代实践:用Map+函数式接口,或者Spring的 @Conditional。
// 优雅的策略模式实现
@Service
public class PaymentStrategy {
private final Map<String, PaymentProcessor> processors;
public PaymentStrategy(List<PaymentProcessor> processorList) {
processors = processorList.stream()
.collect(Collectors.toMap(
PaymentProcessor::getType,
Function.identity()
));
}
public PaymentResult process(String type, PaymentRequest request) {
PaymentProcessor processor = processors.get(type);
if (processor == null) {
throw new IllegalArgumentException("不支持的支付类型: " + type);
}
return processor.process(request);
}
}
3. 适配器模式的实际应用
有用之处:整合第三方库,保持代码整洁。
// 适配第三方支付SDK
@Component
public class WechatPayAdapter implements PaymentProcessor {
private final WechatPayClient wechatPayClient;
@Override
public PaymentResult process(PaymentRequest request) {
// 将我们的Request转换成微信SDK的Request
WechatPayRequest wechatRequest = convertToWechatRequest(request);
// 调用微信SDK
WechatPayResponse response = wechatPayClient.pay(wechatRequest);
// 将微信Response转换成我们的Response
return convertToPaymentResult(response);
}
}
4. 观察者模式的事件驱动
有用之处:解耦业务逻辑。
// Spring的事件机制就很好用
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
public Order createOrder(CreateOrderRequest request) {
Order order = // 创建订单逻辑
// 发布领域事件
eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
return order;
}
}
@Component
@Slf4j
public class OrderEventListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 发送邮件
emailService.sendOrderConfirmation(event.getOrder());
// 更新库存
inventoryService.updateStock(event.getOrder());
// 记录日志
log.info("订单创建: {}", event.getOrder().getId());
}
}
作为技术负责人,我在Code Review时的原则
- 能简单就别复杂:如果if-else能搞定,就别用策略模式;如果直接调用能搞定,就别用适配器。
- 面向变化编程,但不是面向幻想编程:为真实的需求变化做设计,而不是为想象中的“可能”需求做设计。
- 分层要实用,不要教条:三层架构够用就别搞四层,Service层能搞定就别非得拆出Manager、Processor、Handler。
- 技术选型要匹配业务规模:日活1000的系统,别用日活1000万的架构。
- 代码是给人看的:你写的代码,下一个接手的同事能看懂吗?能快速修改吗?
什么时候该用复杂架构?
- 真的遇到性能瓶颈了:数据库扛不住了,再考虑分库分表、CQRS。
- 业务真的复杂到需要DDD了:像电商的库存系统、金融的交易系统。
- 团队规模真的需要了:20人以上的团队协作,需要清晰的架构边界。
- 系统真的需要高可用了:99.99%的SLA要求。
记住:架构的复杂度,应该与业务的复杂度成正比,而不是与程序员的炫技欲望成正比。
现代云原生、微服务、Serverless架构,已经为我们提供了很多优秀的实践。你的目标不是写出能套用某个架构模式的代码,而是写出简单、清晰、易于维护、能快速响应业务变化的代码。
在后端开发中,克制比炫技更重要,务实比理论更重要。共勉!🚀