Skip to content

架构思维

为什么需要架构思维

许多开发者在职业生涯的早期阶段,习惯于关注"如何实现"——选什么技术栈、用什么框架、怎么写代码。但随着系统复杂度增长,这些局部最优解往往导致全局灾难:

  • 一个看似简单的需求变更,需要改动十几个模块
  • 系统性能瓶颈无法通过简单的加机器解决
  • 新人入职半年仍然无法独立开发
  • 微服务拆分后,调用链混乱,排查问题如同噩梦

这些问题的根源在于缺乏架构思维。架构思维不是在画图工具里画几张 UML 图,而是一种系统性思考能力——在需求、技术、团队、时间、成本等多维约束下,做出最优权衡的思维习惯。

架构的三个层次

┌──────────────────────────────────────────────────┐
│  业务架构:领域划分、业务能力、业务流程             │
│  回答:我们要做什么?                              │
├──────────────────────────────────────────────────┤
│  应用架构:模块划分、服务拆分、接口定义             │
│  回答:系统如何组织?                              │
├──────────────────────────────────────────────────┤
│  技术架构:技术选型、部署方案、中间件选择            │
│  回答:用哪些技术实现?                            │
└──────────────────────────────────────────────────┘

架构的定义

架构不仅仅是"系统的结构",它包含三个核心要素:

要素含义示例
结构系统的组件、模块、服务及其关系微服务拓扑、分层架构、模块依赖
决策关键的技术选择和约束选 MySQL 还是 PostgreSQL、同步还是异步
权衡在多个矛盾目标间的取舍一致性 vs 可用性(CAP)、性能 vs 可维护性

"Architecture is about the important stuff. Whatever that is." — Ralph Johnson

SOLID 原则

SOLID 是面向对象设计的五个基本原则,是架构思维的基石。

1. 单一职责原则(SRP, Single Responsibility Principle)

一个类应该只有一个引起它变化的原因。

java
// ❌ 违反 SRP:一个类承担了数据存储、格式化和打印三个职责
class Report {
    public void loadData() { /* 从数据库加载数据 */ }
    public void formatAsHtml() { /* 格式化为 HTML */ }
    public void print() { /* 打印报告 */ }
}

// ✅ 符合 SRP:职责分离
class ReportDataLoader {
    public ReportData load() { /* 从数据库加载 */ }
}

class ReportFormatter {
    public String formatAsHtml(ReportData data) { /* 格式化为 HTML */ }
    public String formatAsPdf(ReportData data) { /* 格式化为 PDF */ }
}

class ReportPrinter {
    public void print(String content) { /* 打印 */ }
}

2. 开闭原则(OCP, Open-Closed Principle)

对扩展开放,对修改关闭。通过抽象和多态实现。

java
// ❌ 违反 OCP:每增加一种通知方式就要修改 sendNotification 方法
class NotificationService {
    public void sendNotification(String type, String message) {
        if ("email".equals(type)) {
            // 发送邮件
        } else if ("sms".equals(type)) {
            // 发送短信
        } else if ("wechat".equals(type)) {
            // 发送微信 —— 每次新增都要改这里
        }
    }
}

// ✅ 符合 OCP:通过抽象和多态,新增通知方式只需添加新类
interface NotificationChannel {
    void send(String message);
}

class EmailChannel implements NotificationChannel {
    public void send(String message) { /* 发送邮件 */ }
}

class SmsChannel implements NotificationChannel {
    public void send(String message) { /* 发送短信 */ }
}

class NotificationService {
    private final List<NotificationChannel> channels;
    
    public NotificationService(List<NotificationChannel> channels) {
        this.channels = channels;
    }
    
    public void sendNotification(String message) {
        channels.forEach(ch -> ch.send(message));
    }
}

3. 里氏替换原则(LSP, Liskov Substitution Principle)

子类必须能够替换父类而不破坏程序的正确性。

java
// ❌ 违反 LSP:正方形不是长方形的正确子类型
class Rectangle {
    protected int width, height;
    public void setWidth(int w) { this.width = w; }
    public void setHeight(int h) { this.height = h; }
    public int getArea() { return width * height; }
}

class Square extends Rectangle {
    @Override
    public void setWidth(int w) {
        this.width = w;
        this.height = w;  // 破坏了父类的行为契约
    }
    @Override
    public void setHeight(int h) {
        this.width = h;
        this.height = h;
    }
}

// 测试会失败:Square 违反了 LSP
void test(Rectangle r) {
    r.setWidth(5);
    r.setHeight(4);
    assert r.getArea() == 20;  // 对于 Square 会是 16
}

// ✅ 正确做法:不继承,各自独立
class Square {
    private int side;
    public void setSide(int s) { this.side = s; }
    public int getArea() { return side * side; }
}

4. 接口隔离原则(ISP, Interface Segregation Principle)

客户端不应该被迫依赖它不使用的方法。

java
// ❌ 违反 ISP:胖接口,强制实现不需要的方法
interface Worker {
    void work();
    void eat();
    void sleep();
}

class Robot implements Worker {
    public void work() { /* 机器人工作 */ }
    public void eat() { throw new UnsupportedOperationException(); } // 无语义
    public void sleep() { throw new UnsupportedOperationException(); }
}

// ✅ 符合 ISP:接口拆分
interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

interface Sleepable {
    void sleep();
}

class Human implements Workable, Eatable, Sleepable {
    public void work() { /* ... */ }
    public void eat() { /* ... */ }
    public void sleep() { /* ... */ }
}

class Robot implements Workable {
    public void work() { /* 机器人工作 */ }
}

5. 依赖倒置原则(DIP, Dependency Inversion Principle)

高层模块不应该依赖低层模块,两者都应该依赖抽象。

java
// ❌ 违反 DIP:UserService 直接依赖具体的 MySQL 实现
class UserService {
    private MySQLUserRepository repository = new MySQLUserRepository();
    
    public User findById(Long id) {
        return repository.findById(id);
    }
}

// ✅ 符合 DIP:依赖抽象接口
interface UserRepository {
    User findById(Long id);
    void save(User user);
}

class MySQLUserRepository implements UserRepository {
    public User findById(Long id) { /* MySQL 实现 */ }
    public void save(User user) { /* MySQL 实现 */ }
}

class UserService {
    private final UserRepository repository; // 依赖接口,不依赖具体实现
    
    public UserService(UserRepository repository) {
        this.repository = repository; // 依赖注入
    }
    
    public User findById(Long id) {
        return repository.findById(id);
    }
}

SOLID 原则总结

原则核心思想一句话记忆
SRP一个类只做一件事"只有一个人能让它改变"
OCP扩展开放,修改关闭"加功能不改变旧代码"
LSP子类可替换父类"儿子能顶替老子"
ISP接口小而专"不要强迫别人用不需要的方法"
DIP依赖抽象不依赖具体"面向接口编程"

领域驱动设计(DDD)

DDD 的核心思想是:将软件设计聚焦于业务领域本身,而不是技术细节。它通过建立领域模型,让代码结构直接反映业务概念。

核心概念

┌──────────────────────────────────────────────────────────────┐
│                    Bounded Context (限界上下文)                  │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                     Ubiquitous Language (统一语言)         │ │
│  │                                                         │ │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────────────────┐   │ │
│  │  │  Entity   │  │   Value   │  │     Aggregate        │   │ │
│  │  │  (实体)   │  │  Object   │  │     (聚合)           │   │ │
│  │  │           │  │  (值对象)  │  │  ┌────────────────┐  │   │ │
│  │  │ 有唯一标识 │  │ 不可变    │  │  │ Aggregate Root │  │   │ │
│  │  │ 有生命周期 │  │ 无标识    │  │  │  (聚合根)      │  │   │ │
│  │  │           │  │ 可替换    │  │  │  对外唯一入口   │  │   │ │
│  │  └──────────┘  └──────────┘  │  └────────────────┘  │   │ │
│  │                              └──────────────────────┘   │ │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │ │
│  │  │  Repository  │  │   Domain     │  │    Domain     │  │ │
│  │  │  (仓储)      │  │   Service    │  │    Event      │  │ │
│  │  │  持久化聚合   │  │   (领域服务)  │  │  (领域事件)   │  │ │
│  │  └──────────────┘  └──────────────┘  └──────────────┘  │ │
│  └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘

Entity(实体)vs Value Object(值对象)

维度Entity(实体)Value Object(值对象)
标识有唯一 ID无 ID,通过属性值判断相等
可变性可变,有生命周期不可变(Immutable)
持久化单独一张表通常嵌入实体表
示例User, Order, ProductMoney, Address, PhoneNumber
java
// Entity:有唯一标识
public class Order {
    private OrderId id;          // 唯一标识
    private OrderStatus status;  // 可变状态
    private Money totalAmount;   // 值对象
    private List<OrderItem> items;
    
    public void confirm() {
        if (this.status != OrderStatus.PENDING) {
            throw new IllegalStateException("只有待确认的订单才能确认");
        }
        this.status = OrderStatus.CONFIRMED;
    }
}

// Value Object:不可变,无标识
public class Money {
    private final BigDecimal amount;
    private final Currency currency;
    
    public Money(BigDecimal amount, Currency currency) {
        this.amount = amount;
        this.currency = currency;
    }
    
    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("货币类型不匹配");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Money)) return false;
        Money money = (Money) o;
        return amount.equals(money.amount) && currency.equals(money.currency);
    }
    
    // 只有 getter,没有 setter
}

Aggregate(聚合)——DDD 的核心模式

聚合是一组相关对象的集合,通过聚合根(Aggregate Root)对外提供唯一访问入口,保证业务规则的一致性。

java
// Order 聚合根:控制整个聚合的一致性边界
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private List<OrderItem> items;  // 只能通过聚合根访问
    private OrderStatus status;
    private Money totalAmount;
    
    // 聚合根提供业务方法,而不是暴露内部集合
    public void addItem(Product product, int quantity, Money price) {
        if (status != OrderStatus.PENDING) {
            throw new DomainException("只有待确认的订单才能添加商品");
        }
        OrderItem item = new OrderItem(product.getId(), quantity, price);
        this.items.add(item);
        recalculateTotal();
    }
    
    public void removeItem(ProductId productId) {
        items.removeIf(item -> item.getProductId().equals(productId));
        recalculateTotal();
    }
    
    private void recalculateTotal() {
        this.totalAmount = items.stream()
            .map(OrderItem::getSubtotal)
            .reduce(Money.ZERO, Money::add);
    }
    
    // 不暴露 items 的 setter,防止外部直接修改破坏一致性
    public List<OrderItem> getItems() {
        return Collections.unmodifiableList(items);
    }
}

Repository(仓储)——领域与持久化的桥梁

java
// 仓储接口定义在领域层,实现在基础设施层
public interface OrderRepository {
    Optional<Order> findById(OrderId id);
    List<Order> findByCustomerId(CustomerId customerId);
    void save(Order order);
    void delete(OrderId id);
}

// 实现在基础设施层,使用 MyBatis/JPA
@Repository
public class MyBatisOrderRepository implements OrderRepository {
    private final OrderMapper orderMapper;
    private final OrderItemMapper orderItemMapper;
    
    @Override
    public Optional<Order> findById(OrderId id) {
        OrderPO orderPO = orderMapper.selectById(id.getValue());
        if (orderPO == null) return Optional.empty();
        List<OrderItemPO> itemPOs = orderItemMapper.selectByOrderId(id.getValue());
        return Optional.of(OrderConverter.toDomain(orderPO, itemPOs));
    }
    
    @Override
    public void save(Order order) {
        OrderPO orderPO = OrderConverter.toPO(order);
        orderMapper.upsert(orderPO);
        // 处理 OrderItem 的增删改...
    }
}

Domain Service(领域服务)

当某个业务逻辑不属于任何单一实体或值对象时,放入领域服务。

java
// 转账业务:涉及两个账户,不属于任何单一 Account 实体
public class TransferService {
    
    public void transfer(Account from, Account to, Money amount) {
        from.debit(amount);      // 扣款
        to.credit(amount);       // 入账
        // 这里可以发布领域事件
    }
}

// 注意区分:领域服务 vs 应用服务
// 领域服务:包含核心业务逻辑,无事务管理
// 应用服务:编排领域对象,管理事务边界,协调基础设施

Bounded Context(限界上下文)

        ┌───────────────────┐         ┌───────────────────┐
        │   商品上下文        │         │   订单上下文        │
        │   (Product)        │────────▶│   (Order)          │
        │                   │ 商品ID   │                   │
        │  Product          │         │  Order             │
        │  - productId      │         │  - orderId         │
        │  - name           │         │  - productId (引用) │
        │  - price          │         │  - quantity        │
        │  - stock          │         │  - unitPrice       │
        │  - category       │         │                    │
        └───────────────────┘         └───────────────────┘
                │                              │
                │         ┌───────────────────┐│
                │         │   用户上下文        ││
                └────────▶│   (User)          │◀┘
                          │                   │
                          │  User             │
                          │  - userId         │
                          │  - name           │
                          │  - address        │
                          └───────────────────┘

关键原则:每个限界上下文内部使用统一的领域模型,上下文之间通过明确的方式(API、事件、共享内核)通信。同一个概念(如"商品")在不同上下文中可能有不同的模型和含义。

六边形架构(端口与适配器)

六边形架构(Hexagonal Architecture),又称端口与适配器架构(Ports & Adapters),由 Alistair Cockburn 提出,核心思想是将业务逻辑与外部依赖完全隔离

                ┌──────────────────────────────────┐
                │         应用层 / 领域层            │
                │     (纯业务逻辑,零外部依赖)        │
                │                                  │
                │   ┌──────────────────────────┐   │
                │   │      Domain Model        │   │
                │   │  (Entities, Value Objects,│   │
                │   │   Domain Services)        │   │
                │   └──────────────────────────┘   │
                │                                  │
                │   ┌──────────────────────────┐   │
                │   │   Application Services    │   │
                │   │   (Use Cases, Ports)      │   │
                │   └──────────────────────────┘   │
                │                                  │
                └──────┬───────────────┬────────────┘
                       │   Ports (端口) │
          ┌────────────┴───┐       ┌───┴────────────┐
          │  驱动端适配器    │       │  被驱动端适配器   │
          │  (Primary)      │       │  (Secondary)    │
          ├────────────────┤       ├────────────────┤
          │ · REST API      │       │ · MySQL 适配器   │
          │ · GraphQL       │       │ · Redis 适配器   │
          │ · gRPC          │       │ · Kafka 适配器   │
          │ · CLI           │       │ · S3 适配器      │
          │ · Message Queue │       │ · Email 适配器   │
          └────────────────┘       └────────────────┘

代码示例

java
// ===== 端口(Port)—— 接口定义在领域层 =====
public interface OrderRepository {
    Optional<Order> findById(OrderId id);
    void save(Order order);
}

public interface PaymentGateway {
    PaymentResult charge(Money amount, PaymentMethod method);
}

public interface NotificationService {
    void sendOrderConfirmation(Order order);
}

// ===== 领域服务(纯业务逻辑) =====
public class OrderPlacementService {
    private final OrderRepository orderRepository;
    private final PaymentGateway paymentGateway;
    private final NotificationService notificationService;
    
    // 依赖注入端口接口,不关心具体实现
    public OrderPlacementService(OrderRepository orderRepository,
                                  PaymentGateway paymentGateway,
                                  NotificationService notificationService) {
        this.orderRepository = orderRepository;
        this.paymentGateway = paymentGateway;
        this.notificationService = notificationService;
    }
    
    public Order placeOrder(PlaceOrderCommand command) {
        // 1. 创建订单
        Order order = Order.create(command.getCustomerId(), command.getItems());
        
        // 2. 收款
        PaymentResult result = paymentGateway.charge(
            order.getTotalAmount(), command.getPaymentMethod());
        if (!result.isSuccess()) {
            throw new PaymentFailedException(result.getErrorMessage());
        }
        
        // 3. 持久化
        order.confirm();
        orderRepository.save(order);
        
        // 4. 通知
        notificationService.sendOrderConfirmation(order);
        
        return order;
    }
}

// ===== 适配器(Adapter)—— 实现在基础设施层 =====
@Repository
public class MyBatisOrderRepository implements OrderRepository {
    // 之前已展示
}

@Service
public class StripePaymentGateway implements PaymentGateway {
    @Override
    public PaymentResult charge(Money amount, PaymentMethod method) {
        // 调用 Stripe API
    }
}

@Service
public class EmailNotificationService implements NotificationService {
    @Override
    public void sendOrderConfirmation(Order order) {
        // 发送邮件
    }
}

六边形架构的优势

优势说明
可测试性领域逻辑可以完全单元测试,只需 mock 端口
可替换性更换数据库、消息队列等只需替换适配器
延迟决策早期可以先用内存实现,后期再切换到真实基础设施
关注点分离业务逻辑和技术实现完全解耦

事件驱动架构

事件驱动架构将系统组件间的通信方式从"请求-响应"转为"发布-订阅"。

┌──────────┐   发布事件    ┌──────────────┐   订阅事件    ┌──────────┐
│ 订单服务  │──────────────▶│   Event Bus  │──────────────▶│ 库存服务  │
│          │              │  (Kafka/Rab-  │              │          │
│ "订单已创建"│              │   bitMQ/SQS) │              │ "扣减库存" │
└──────────┘              └──────┬───────┘              └──────────┘

                                 │ 订阅事件

                          ┌──────────┐
                          │ 通知服务  │
                          │          │
                          │ "发送邮件" │
                          └──────────┘

事件处理方式

方式描述适用场景
简单事件通知事件发生后通知其他服务订单创建后发邮件
事件携带状态(Event-Carried State Transfer)事件中包含完整的业务数据订单服务将订单数据通过事件传递给分析服务
事件溯源(Event Sourcing)所有状态变更存储为事件序列审计、回溯、CQRS
java
// 领域事件定义
public class OrderCreatedEvent {
    private final OrderId orderId;
    private final CustomerId customerId;
    private final Money totalAmount;
    private final Instant occurredAt;
    // 构造函数、getter...
}

// 事件发布
@Service
public class OrderService {
    private final ApplicationEventPublisher eventPublisher;
    
    public Order createOrder(CreateOrderCommand cmd) {
        Order order = Order.create(cmd.getCustomerId(), cmd.getItems());
        orderRepository.save(order);
        
        // 发布领域事件
        eventPublisher.publishEvent(new OrderCreatedEvent(
            order.getId(), order.getCustomerId(), 
            order.getTotalAmount(), Instant.now()));
        
        return order;
    }
}

// 事件订阅(异步处理)
@Component
public class InventoryHandler {
    
    @EventListener
    @Async
    public void handleOrderCreated(OrderCreatedEvent event) {
        // 扣减库存
        inventoryService.deductStock(event.getOrderId());
    }
}

CQRS(命令查询职责分离)

CQRS 将系统的读操作和写操作分离到不同的模型中。

         ┌──────────────────────────────────────┐
         │              客户端                   │
         └──────┬───────────────────┬───────────┘
                │                   │
         ┌──────▼──────┐     ┌──────▼──────┐
         │  Command     │     │   Query     │
         │  (写操作)    │     │  (读操作)    │
         │             │     │             │
         │  Command    │     │   Query     │
         │  Model      │     │   Model     │
         │  (领域模型)  │     │  (读模型)    │
         │             │     │             │
         │  MySQL      │     │  Elastic-   │
         │  (写库)     │     │  search     │
         └──────┬──────┘     │  (读库)     │
                │            └─────────────┘
                │ 事件同步

         ┌──────────────┐
         │   Event Bus   │
         └──────────────┘
java
// ===== Command 端(写) =====
public class CreateOrderCommand {
    private final CustomerId customerId;
    private final List<OrderItemCommand> items;
    // 构造函数、getter...
}

@Service
public class OrderCommandService {
    private final OrderRepository orderRepository;
    
    @Transactional
    public OrderId handle(CreateOrderCommand command) {
        Order order = Order.create(command.getCustomerId(), command.getItems());
        orderRepository.save(order);
        // 事件会触发读模型更新
        return order.getId();
    }
}

// ===== Query 端(读) =====
public class OrderSummaryQuery {
    private final OrderId orderId;
    // 构造函数、getter...
}

public class OrderSummary {
    private String orderId;
    private String customerName;
    private BigDecimal totalAmount;
    private String status;
    private List<OrderItemSummary> items;
    // getter...
}

@Service
public class OrderQueryService {
    private final OrderReadRepository readRepository;
    
    public OrderSummary handle(OrderSummaryQuery query) {
        // 从优化过的读模型查询(可能来自 Elasticsearch、Redis 或专门的读表)
        return readRepository.findOrderSummary(query.getOrderId());
    }
}

CQRS 适用场景

场景是否适合 CQRS原因
简单的 CRUD 系统引入复杂度大于收益
读写负载差异大读写分离后可以独立扩容
复杂查询需求读模型可以针对查询优化,无需受写模型约束
需要事件溯源CQRS 与事件溯源天然契合
高并发写场景写模型专注于领域逻辑,不受读模型影响

设计模式在架构中的应用

设计模式架构应用场景示例
策略模式运行时选择算法支付方式选择、负载均衡策略、路由策略
观察者模式系统间解耦通信事件驱动架构、消息队列、Spring Event
装饰器模式增强组件功能缓存层、日志层、熔断器包装
模板方法定义流程骨架框架的启动/关闭流程、批处理框架
工厂模式对象创建解耦DI 容器、SPI 加载、连接池创建
适配器模式对接外部系统六边形架构的适配器、第三方 API 封装
门面模式简化复杂子系统API Gateway、对外暴露的统一接口
代理模式控制对象访问RPC 代理、AOP、延迟加载、安全代理

架构决策记录(ADR)

ADR(Architecture Decision Record)是记录架构决策的轻量级文档,帮助团队理解"为什么系统是这样设计的"。

ADR 模板

markdown
# ADR-001: 选择 MySQL 作为主数据库

## 状态
已采纳

## 背景
新项目需要选择关系型数据库作为主存储。

## 决策
选择 MySQL 8.0 作为主数据库。

## 考虑因素
- 团队对 MySQL 经验丰富,有成熟的运维体系
- 需要事务支持(ACID),MySQL InnoDB 满足需求
- 社区活跃,文档完善,问题易于排查
- 数据量预估 3 年内不超过 500GB,MySQL 完全够用

## 替代方案
| 方案 | 优点 | 缺点 | 决定 |
|------|------|------|------|
| PostgreSQL | 功能更丰富,JSON 支持好 | 团队不熟悉,运维成本高 | 不采用 |
| MongoDB | 灵活 Schema | 需要事务的场景不适用 | 不采用 |
| TiDB | 水平扩展强 | 复杂度高,小团队维护困难 | 不采用 |

## 后果
- 需要自行处理分库分表(如果未来数据量超预期)
- 读写分离需要中间件(如 ShardingSphere)
- 监控告警需要接入 MySQL Exporter

ADR 的价值

  • 防止重复讨论:已决策的问题不再反复争论
  • 新人 onboarding:了解系统演进历史
  • 架构评审:作为评审依据和参考
  • 技术债追踪:记录哪些决策是临时方案,需要在未来重新审视

面试常见问题

1. 什么是 SOLID 原则?请举例说明。

答:SOLID 是面向对象设计的五个基本原则:SRP(单一职责)——一个类只做一件事,如将报表的数据加载、格式化、打印拆分为三个类;OCP(开闭原则)——对扩展开放对修改关闭,通过接口和多态实现,如策略模式;LSP(里氏替换)——子类必须能替换父类,经典的"正方形不是长方形"问题;ISP(接口隔离)——接口应小而专,如将 Worker 接口拆分为 Workable 和 Eatable;DIP(依赖倒置)——依赖抽象而非具体实现,如 Service 依赖 Repository 接口而非具体实现类。

2. DDD 中 Entity 和 Value Object 的区别是什么?

答:Entity(实体)有唯一标识(ID),即使属性完全相同,ID 不同就是不同对象,它有生命周期,状态可变;Value Object(值对象)没有唯一标识,通过属性值判断相等性,它应该是不可变的(Immutable),被替换而非修改。典型例子:User 是 Entity(有 userId),Money 是 Value Object(金额和币种相同就是同一个钱)。

3. 六边形架构解决了什么问题?

答:六边形架构通过端口(Port)和适配器(Adapter)将业务逻辑与外部依赖完全隔离。它解决了传统分层架构中业务逻辑依赖基础设施的问题(如 Service 直接依赖 DAO)。好处是:业务逻辑可以完全单元测试(mock 端口即可),外部依赖可以随时替换(换数据库只需换适配器),技术决策可以延迟(早期用内存实现,后期再切换)。

4. CQRS 适合什么场景?不适合什么场景?

答:适合:读写负载差异大需要独立扩容、复杂查询需求(读模型可针对查询优化)、需要事件溯源、高并发写场景。不适合:简单 CRUD 系统(引入复杂度大于收益)、团队经验不足、数据一致性要求极高且无法接受最终一致性的场景。

5. 为什么需要 ADR(架构决策记录)?

答:ADR 记录架构决策的上下文、考虑因素和后果。它解决了"为什么系统是这样设计的"这类问题——当新人加入团队或几个月后回顾代码时,能理解当初的决策背景。它防止团队对已决策的问题反复争论,记录技术债和临时方案,供未来重新审视和重构。