⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7601159804491333651 「掘金者阿豪」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

最近做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();
}

// 具体使用
publicclass 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
publicclass 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
publicclass 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
publicclass 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
publicclass 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,用实现类实现。但别为了抽象而抽象。

// 有用的抽象:定义清晰的契约
publicinterface PaymentService {
PaymentResult pay(PaymentRequest request);
RefundResult refund(RefundRequest request);
}

@Service
publicclass AlipayService implements PaymentService {
// 具体实现
}

// 使用时依赖接口
@Service
publicclass OrderService {
privatefinal PaymentService paymentService; // 依赖抽象

public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}

2. 策略模式的精简版

有用之处:消除if-else,让代码更清晰。

现代实践:用Map+函数式接口,或者Spring的@Conditional。

// 优雅的策略模式实现
@Service
publicclass PaymentStrategy {
privatefinal 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) {
thrownew IllegalArgumentException("不支持的支付类型: " + type);
}
return processor.process(request);
}
}

3. 适配器模式的实际应用

有用之处:整合第三方库,保持代码整洁。

// 适配第三方支付SDK
@Component
publicclass WechatPayAdapter implements PaymentProcessor {
privatefinal 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
publicclass OrderService {
privatefinal ApplicationEventPublisher eventPublisher;

public Order createOrder(CreateOrderRequest request) {
Order order = // 创建订单逻辑

// 发布领域事件
eventPublisher.publishEvent(new OrderCreatedEvent(this, order));

return order;
}
}

@Component
@Slf4j
publicclass OrderEventListener {

@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 发送邮件
emailService.sendOrderConfirmation(event.getOrder());

// 更新库存
inventoryService.updateStock(event.getOrder());

// 记录日志
log.info("订单创建: {}", event.getOrder().getId());
}
}

作为技术负责人,我在Code Review时的原则

  1. 能简单就别复杂:如果if-else能搞定,就别用策略模式;如果直接调用能搞定,就别用适配器。
  2. 面向变化编程,但不是面向幻想编程:为真实的需求变化做设计,而不是为想象中的“可能”需求做设计。
  3. 分层要实用,不要教条:三层架构够用就别搞四层,Service层能搞定就别非得拆出Manager、Processor、Handler。
  4. 技术选型要匹配业务规模:日活1000的系统,别用日活1000万的架构。
  5. 代码是给人看的:你写的代码,下一个接手的同事能看懂吗?能快速修改吗?

什么时候该用复杂架构?

  1. 真的遇到性能瓶颈了:数据库扛不住了,再考虑分库分表、CQRS。
  2. 业务真的复杂到需要DDD了:像电商的库存系统、金融的交易系统。
  3. 团队规模真的需要了:20人以上的团队协作,需要清晰的架构边界。
  4. 系统真的需要高可用了:99.99%的SLA要求。

记住:架构的复杂度,应该与业务的复杂度成正比,而不是与程序员的炫技欲望成正比。

现代云原生、微服务、Serverless架构,已经为我们提供了很多优秀的实践。你的目标不是写出能套用某个架构模式的代码,而是写出简单、清晰、易于维护、能快速响应业务变化的代码。

在后端开发中,克制比炫技更重要,务实比理论更重要。共勉!🚀

文章目录
  1. 1. 我们为什么会陷入架构模式的陷阱?
  2. 2. 有哪些水土不服的架构模式?
    1. 2.1. 1. 抽象工厂模式(Abstract Factory)
    2. 2.2. 2. 复杂的事件溯源(Event Sourcing)
    3. 2.3. 3. CQRS(命令查询职责分离)的滥用
    4. 2.4. 4. 过度设计的领域驱动设计(DDD)
  3. 3. 那剩下10%有用的,是什么?
    1. 3.1. 1. 清晰的依赖方向
    2. 3.2. 2. 策略模式的精简版
    3. 3.3. 3. 适配器模式的实际应用
    4. 3.4. 4. 观察者模式的事件驱动
  4. 4. 作为技术负责人,我在Code Review时的原则
  5. 5. 什么时候该用复杂架构?