Appearance
架构思维
为什么需要架构思维
许多开发者在职业生涯的早期阶段,习惯于关注"如何实现"——选什么技术栈、用什么框架、怎么写代码。但随着系统复杂度增长,这些局部最优解往往导致全局灾难:
- 一个看似简单的需求变更,需要改动十几个模块
- 系统性能瓶颈无法通过简单的加机器解决
- 新人入职半年仍然无法独立开发
- 微服务拆分后,调用链混乱,排查问题如同噩梦
这些问题的根源在于缺乏架构思维。架构思维不是在画图工具里画几张 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, Product | Money, 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 ExporterADR 的价值
- 防止重复讨论:已决策的问题不再反复争论
- 新人 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 记录架构决策的上下文、考虑因素和后果。它解决了"为什么系统是这样设计的"这类问题——当新人加入团队或几个月后回顾代码时,能理解当初的决策背景。它防止团队对已决策的问题反复争论,记录技术债和临时方案,供未来重新审视和重构。
