










软件架构是一个系统的高层结构,它定义了系统的组织方式、组件之间的关系以及指导设计和演化的原则。好的架构能够让系统易于理解、易于修改、易于测试,并且能够满足功能性和非功能性需求。
本文将系统性地介绍常见的软件架构设计模式,包括分层架构、微服务架构、事件驱动架构、CQRS、领域驱动设计等,并结合实际案例进行分析。
分层架构是最经典、最广泛使用的架构模式之一。它将系统划分为若干层,每一层只与相邻层交互,形成清晰的职责边界。
典型的四层架构:
# 分层架构示例
class UserRepository:
"""数据访问层"""
def find_by_id(self, user_id: int):
return db.query(f"SELECT * FROM users WHERE id = {user_id}")
class UserService:
"""业务逻辑层"""
def __init__(self, repo: UserRepository):
self.repo = repo
def get_user_profile(self, user_id: int):
user = self.repo.find_by_id(user_id)
if not user:
raise ValueError("User not found")
return {"id": user.id, "name": user.name, "email": user.email}
class UserController:
"""表现层"""
def __init__(self, service: UserService):
self.service = service
def handle_get_user(self, request):
user_id = request.params.get("id")
return self.service.get_user_profile(user_id)
微服务架构将一个大型应用拆分为一组小型、独立部署的服务。每个服务运行在自己的进程中,通过轻量级机制(通常是 HTTP API 或消息队列)进行通信。
按业务能力拆分(Business Capability)
这是最推荐的拆分方式,按照业务领域来划分服务边界:
按子域拆分(Subdomain)
结合领域驱动设计,识别核心域、支撑域和通用域,分别建立对应的微服务。
同步通信:
异步通信:
| 挑战 | 解决方案 |
|---|---|
| 分布式事务 | Saga 模式、两阶段提交 |
| 服务发现 | Consul、Eureka、Kubernetes DNS |
| 负载均衡 | Nginx、Envoy、Istio |
| 熔断降级 | Hystrix、Resilience4j |
| 链路追踪 | Jaeger、Zipkin、SkyWalking |
| 配置管理 | Apollo、Nacos、Consul |
事件驱动架构(EDA)是一种以事件为核心的架构模式。系统中的组件通过发布和订阅事件来进行通信,生产者和消费者之间完全解耦。
事件(Event)
事件是系统中发生的某件事情的记录,通常是不可变的。例如:
OrderPlaced(订单已下单)PaymentCompleted(支付已完成)UserRegistered(用户已注册)事件生产者(Event Producer)
负责产生事件并发布到事件总线或消息队列。
事件消费者(Event Consumer)
订阅感兴趣的事件并进行处理。一个事件可以有多个消费者。
事件总线(Event Bus)
负责事件的路由和分发,常见实现有 Kafka、RabbitMQ、AWS EventBridge 等。
事件溯源是一种特殊的数据存储方式,不存储当前状态,而是存储所有导致当前状态的事件序列。
优点:
缺点:
# 事件溯源示例
from dataclasses import dataclass
from typing import List
from datetime import datetime
@dataclass
class Event:
event_type: str
timestamp: datetime
data: dict
class BankAccount:
def __init__(self, account_id: str):
self.account_id = account_id
self.balance = 0
self.events: List[Event] = []
def deposit(self, amount: float):
event = Event(
event_type="MoneyDeposited",
timestamp=datetime.now(),
data={"amount": amount}
)
self._apply(event)
self.events.append(event)
def withdraw(self, amount: float):
if amount > self.balance:
raise ValueError("Insufficient funds")
event = Event(
event_type="MoneyWithdrawn",
timestamp=datetime.now(),
data={"amount": amount}
)
self._apply(event)
self.events.append(event)
def _apply(self, event: Event):
if event.event_type == "MoneyDeposited":
self.balance += event.data["amount"]
elif event.event_type == "MoneyWithdrawn":
self.balance -= event.data["amount"]
@classmethod
def rebuild_from_events(cls, account_id: str, events: List[Event]):
account = cls(account_id)
for event in events:
account._apply(event)
return account
CQRS 将系统的读操作(Query)和写操作(Command)分离到不同的模型中。
命令端(Write Side)
查询端(Read Side)
用户请求
│
├── 写操作 ──→ Command Handler ──→ Domain Model ──→ Event Store
│ │
│ ↓
└── 读操作 ──→ Query Handler ──→ Read Model ←── Event Handler
限界上下文(Bounded Context)
限界上下文是 DDD 中最重要的概念之一。它定义了一个模型的适用边界,在这个边界内,所有的术语、概念和规则都有明确且一致的含义。
不同限界上下文之间通过上下文映射(Context Map)来描述关系:
通用语言(Ubiquitous Language)
在限界上下文内,开发人员和领域专家使用同一套语言来描述业务概念。这套语言应该体现在代码中,避免"翻译"带来的信息损失。
实体(Entity)
具有唯一标识的对象,其标识在整个生命周期内保持不变。
class Order:
def __init__(self, order_id: str, customer_id: str):
self.order_id = order_id # 唯一标识
self.customer_id = customer_id
self.items = []
self.status = "PENDING"
def add_item(self, product_id: str, quantity: int, price: float):
self.items.append({
"product_id": product_id,
"quantity": quantity,
"price": price
})
def confirm(self):
if not self.items:
raise ValueError("Cannot confirm empty order")
self.status = "CONFIRMED"
值对象(Value Object)
没有唯一标识,通过属性值来判断相等性的对象。值对象应该是不可变的。
from dataclasses import dataclass
@dataclass(frozen=True)
class Money:
amount: float
currency: str
def add(self, other: 'Money') -> 'Money':
if self.currency != other.currency:
raise ValueError("Cannot add different currencies")
return Money(self.amount + other.amount, self.currency)
def multiply(self, factor: float) -> 'Money':
return Money(self.amount * factor, self.currency)
聚合(Aggregate)
聚合是一组相关对象的集群,作为数据修改的单元。每个聚合有一个根实体(Aggregate Root),外部只能通过根实体访问聚合内的对象。
领域服务(Domain Service)
当某个业务逻辑不自然地属于任何实体或值对象时,可以将其放入领域服务中。
领域事件(Domain Event)
表示领域中发生的重要事情,用于在聚合之间传递信息,保持松耦合。
六边形架构,也称为端口与适配器架构(Ports and Adapters),由 Alistair Cockburn 提出。其核心思想是将应用程序的业务逻辑与外部系统(数据库、UI、消息队列等)完全隔离。
应用核心(Application Core)
包含业务逻辑,完全不依赖任何外部框架或基础设施。
端口(Port)
定义应用核心与外部世界交互的接口(抽象)。分为:
适配器(Adapter)
实现端口接口,负责技术细节的转换。例如:
外部世界
│
├── HTTP 请求 ──→ [HTTP Adapter] ──→ (Driving Port) ──→ Application Core
├── CLI 命令 ──→ [CLI Adapter] ──→ (Driving Port) ──→
│
└── Application Core ──→ (Driven Port) ──→ [DB Adapter] ──→ PostgreSQL
──→ [Cache Adapter] ──→ Redis
──→ [MQ Adapter] ──→ Kafka
# 端口定义(接口)
from abc import ABC, abstractmethod
class UserRepositoryPort(ABC):
@abstractmethod
def save(self, user) -> None: ...
@abstractmethod
def find_by_id(self, user_id: str): ...
class EmailServicePort(ABC):
@abstractmethod
def send_welcome_email(self, email: str, name: str) -> None: ...
# 应用核心(不依赖任何具体技术)
class RegisterUserUseCase:
def __init__(
self,
user_repo: UserRepositoryPort,
email_service: EmailServicePort
):
self.user_repo = user_repo
self.email_service = email_service
def execute(self, command):
user = User(id=generate_id(), name=command.name, email=command.email)
self.user_repo.save(user)
self.email_service.send_welcome_email(user.email, user.name)
return user
# 适配器实现
class PostgresUserRepository(UserRepositoryPort):
def save(self, user) -> None:
db.execute("INSERT INTO users ...", user)
def find_by_id(self, user_id: str):
return db.query("SELECT * FROM users WHERE id = ?", user_id)
class SendGridEmailService(EmailServicePort):
def send_welcome_email(self, email: str, name: str) -> None:
sendgrid_client.send(to=email, template="welcome", data={"name": name})
Serverless 架构让开发者无需管理服务器基础设施,只需关注业务逻辑代码。云提供商负责自动扩缩容、高可用等运维工作。
架构决策记录(Architecture Decision Record)是一种轻量级的文档实践,用于记录重要的架构决策、决策背景、考虑过的方案以及最终选择的理由。
# ADR-001: 选择消息队列技术
## 状态
已接受
## 背景
系统需要在订单服务和库存服务之间进行异步通信,
需要选择合适的消息队列技术。
## 决策
选择 Apache Kafka 作为消息队列。
## 考虑的方案
### 方案一:RabbitMQ
- 优点:成熟稳定,支持多种消息协议,管理界面友好
- 缺点:消息不持久化(默认),吞吐量相对较低
### 方案二:Apache Kafka
- 优点:高吞吐量,消息持久化,支持消息回放,生态丰富
- 缺点:运维复杂,学习曲线较高
### 方案三:AWS SQS
- 优点:全托管,无运维负担
- 缺点:供应商锁定,功能相对简单
## 结果
选择 Kafka,因为系统对吞吐量要求高,且需要消息回放能力
用于数据分析和故障恢复。
## 后果
- 需要团队学习 Kafka 运维知识
- 需要部署 Zookeeper(或使用 KRaft 模式)
- 获得了高吞吐量和消息持久化能力
ATAM 是一种系统化的架构评估方法,通过识别架构决策与质量属性之间的权衡关系来评估架构。
主要步骤:
在架构评估中,常见的质量属性包括:
| 质量属性 | 描述 | 常见度量指标 |
|---|---|---|
| 性能 | 系统响应时间和吞吐量 | 响应时间 P99、TPS |
| 可用性 | 系统正常运行的时间比例 | SLA(如 99.9%) |
| 可扩展性 | 系统处理增长负载的能力 | 水平/垂直扩展能力 |
| 安全性 | 系统抵御攻击的能力 | 漏洞数量、合规性 |
| 可维护性 | 系统修改和演化的难易程度 | 代码复杂度、测试覆盖率 |
| 可测试性 | 系统验证行为的难易程度 | 单元测试覆盖率 |
| 可部署性 | 系统部署的频率和风险 | 部署频率、MTTR |
| 架构模式 | 复杂度 | 适用规模 | 主要优势 | 主要挑战 |
|---|---|---|---|---|
| 分层架构 | 低 | 小到中型 | 简单易懂 | 可能变得臃肿 |
| 微服务 | 高 | 中到大型 | 独立部署、技术多样性 | 分布式复杂性 |
| 事件驱动 | 中到高 | 中到大型 | 松耦合、高扩展性 | 最终一致性 |
| 六边形架构 | 中 | 任意规模 | 业务逻辑隔离 | 初期设计成本 |
| Serverless | 中 | 小到中型 | 无运维、弹性 | 冷启动、调试 |
初创团队 / 小型项目
从单体架构或分层架构开始,不要过早引入微服务。等到团队规模增长、业务复杂度提升后,再考虑拆分。
中型团队 / 成长期产品
可以考虑模块化单体(Modular Monolith)作为过渡,保持代码内部的模块边界清晰,为未来拆分做准备。
大型团队 / 复杂业务
微服务架构配合 DDD 是较好的选择。但要注意:
事件密集型系统
事件驱动架构非常适合需要处理大量异步事件的系统,如 IoT 数据处理、实时分析、通知系统等。
本文档涵盖了主流软件架构模式的核心概念、优缺点和适用场景,供架构设计参考使用。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。