第一章:引言

软件架构是一个系统的高层结构,它定义了系统的组织方式、组件之间的关系以及指导设计和演化的原则。好的架构能够让系统易于理解、易于修改、易于测试,并且能够满足功能性和非功能性需求。

本文将系统性地介绍常见的软件架构设计模式,包括分层架构、微服务架构、事件驱动架构、CQRS、领域驱动设计等,并结合实际案例进行分析。


第二章:分层架构(Layered Architecture)

2.1 概述

分层架构是最经典、最广泛使用的架构模式之一。它将系统划分为若干层,每一层只与相邻层交互,形成清晰的职责边界。

典型的四层架构:

  1. 表现层(Presentation Layer):负责用户界面和用户交互
  2. 业务逻辑层(Business Logic Layer):负责核心业务规则
  3. 数据访问层(Data Access Layer):负责数据的读写操作
  4. 数据库层(Database Layer):负责数据的持久化存储

2.2 优点

  • 关注点分离,每层职责明确
  • 易于理解和维护
  • 层与层之间可以独立替换
  • 便于团队分工协作

2.3 缺点

  • 可能出现”污水池”反模式,请求只是简单地穿透各层而不做任何处理
  • 性能开销:每次请求都需要经过多层处理
  • 在大型系统中,层内可能变得臃肿

2.4 适用场景

  • 传统企业应用
  • 内部管理系统
  • 对性能要求不极端的业务系统
# 分层架构示例
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)

第三章:微服务架构(Microservices Architecture)

3.1 概述

微服务架构将一个大型应用拆分为一组小型、独立部署的服务。每个服务运行在自己的进程中,通过轻量级机制(通常是 HTTP API 或消息队列)进行通信。

3.2 核心原则

  • 单一职责:每个服务只负责一个业务能力
  • 独立部署:每个服务可以独立构建、测试和部署
  • 去中心化:数据管理和治理去中心化
  • 容错设计:服务之间的通信需要考虑失败情况

3.3 服务拆分策略

按业务能力拆分(Business Capability)

这是最推荐的拆分方式,按照业务领域来划分服务边界:

  • 用户服务(User Service)
  • 订单服务(Order Service)
  • 支付服务(Payment Service)
  • 库存服务(Inventory Service)
  • 通知服务(Notification Service)

按子域拆分(Subdomain)

结合领域驱动设计,识别核心域、支撑域和通用域,分别建立对应的微服务。

3.4 通信模式

同步通信:

  • REST over HTTP/HTTPS
  • gRPC(高性能场景)
  • GraphQL(灵活查询场景)

异步通信:

  • 消息队列(RabbitMQ、Kafka)
  • 事件总线
  • 发布/订阅模式

3.5 挑战与解决方案

挑战 解决方案
分布式事务 Saga 模式、两阶段提交
服务发现 Consul、Eureka、Kubernetes DNS
负载均衡 Nginx、Envoy、Istio
熔断降级 Hystrix、Resilience4j
链路追踪 Jaeger、Zipkin、SkyWalking
配置管理 Apollo、Nacos、Consul

第四章:事件驱动架构(Event-Driven Architecture)

4.1 概述

事件驱动架构(EDA)是一种以事件为核心的架构模式。系统中的组件通过发布和订阅事件来进行通信,生产者和消费者之间完全解耦。

4.2 核心概念

事件(Event)

事件是系统中发生的某件事情的记录,通常是不可变的。例如:

  • OrderPlaced(订单已下单)
  • PaymentCompleted(支付已完成)
  • UserRegistered(用户已注册)

事件生产者(Event Producer)

负责产生事件并发布到事件总线或消息队列。

事件消费者(Event Consumer)

订阅感兴趣的事件并进行处理。一个事件可以有多个消费者。

事件总线(Event Bus)

负责事件的路由和分发,常见实现有 Kafka、RabbitMQ、AWS EventBridge 等。

4.3 事件溯源(Event Sourcing)

事件溯源是一种特殊的数据存储方式,不存储当前状态,而是存储所有导致当前状态的事件序列。

优点:

  • 完整的审计日志
  • 可以重放事件重建任意时间点的状态
  • 天然支持时间旅行调试

缺点:

  • 查询当前状态需要重放所有事件(可用快照优化)
  • 事件 schema 变更复杂
  • 学习曲线较高
# 事件溯源示例
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

4.4 CQRS(命令查询职责分离)

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

命令端(Write Side)

  • 处理业务命令
  • 维护写模型(通常是领域模型)
  • 发布领域事件

查询端(Read Side)

  • 处理查询请求
  • 维护读模型(通常是针对查询优化的扁平化视图)
  • 订阅领域事件,更新读模型
用户请求
    │
    ├── 写操作 ──→ Command Handler ──→ Domain Model ──→ Event Store
    │                                                        │
    │                                                        ↓
    └── 读操作 ──→ Query Handler ──→ Read Model ←── Event Handler

第五章:领域驱动设计(Domain-Driven Design)

5.1 战略设计

限界上下文(Bounded Context)

限界上下文是 DDD 中最重要的概念之一。它定义了一个模型的适用边界,在这个边界内,所有的术语、概念和规则都有明确且一致的含义。

不同限界上下文之间通过上下文映射(Context Map)来描述关系:

  • 共享内核(Shared Kernel):两个上下文共享部分模型
  • 客户/供应商(Customer/Supplier):上游提供服务,下游消费
  • 防腐层(Anti-Corruption Layer):隔离外部系统的影响
  • 开放主机服务(Open Host Service):提供标准化的集成接口
  • 发布语言(Published Language):使用共享的语言进行集成

通用语言(Ubiquitous Language)

在限界上下文内,开发人员和领域专家使用同一套语言来描述业务概念。这套语言应该体现在代码中,避免”翻译”带来的信息损失。

5.2 战术设计

实体(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)

表示领域中发生的重要事情,用于在聚合之间传递信息,保持松耦合。


第六章:六边形架构(Hexagonal Architecture)

6.1 概述

六边形架构,也称为端口与适配器架构(Ports and Adapters),由 Alistair Cockburn 提出。其核心思想是将应用程序的业务逻辑与外部系统(数据库、UI、消息队列等)完全隔离。

6.2 核心概念

应用核心(Application Core)

包含业务逻辑,完全不依赖任何外部框架或基础设施。

端口(Port)

定义应用核心与外部世界交互的接口(抽象)。分为:

  • 驱动端口(Driving Port):外部调用应用核心的接口
  • 被驱动端口(Driven Port):应用核心调用外部系统的接口

适配器(Adapter)

实现端口接口,负责技术细节的转换。例如:

  • HTTP 适配器:将 HTTP 请求转换为应用命令
  • 数据库适配器:实现仓储接口,与具体数据库交互
  • 消息队列适配器:实现消息发布接口
外部世界
  │
  ├── 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

6.3 实现示例

# 端口定义(接口)
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 架构

7.1 概述

Serverless 架构让开发者无需管理服务器基础设施,只需关注业务逻辑代码。云提供商负责自动扩缩容、高可用等运维工作。

7.2 核心特征

  • 无服务器管理:开发者不需要配置或管理服务器
  • 按需执行:函数只在被调用时运行
  • 自动扩缩容:根据请求量自动调整
  • 按使用付费:只为实际执行的时间和资源付费

7.3 适用场景

  • 事件驱动的数据处理(文件上传触发处理)
  • API 后端(低频或突发流量)
  • 定时任务
  • 实时流处理
  • 聊天机器人和 AI 推理

7.4 挑战

  • 冷启动延迟:函数首次调用时需要初始化
  • 执行时间限制:通常有最大执行时间限制
  • 调试困难:分布式追踪和本地调试复杂
  • 供应商锁定:不同云厂商的 API 差异较大
  • 状态管理:函数本身是无状态的,需要外部存储

第八章:架构决策记录(ADR)

8.1 什么是 ADR

架构决策记录(Architecture Decision Record)是一种轻量级的文档实践,用于记录重要的架构决策、决策背景、考虑过的方案以及最终选择的理由。

8.2 ADR 模板

# ADR-001: 选择消息队列技术

## 状态
已接受

## 背景
系统需要在订单服务和库存服务之间进行异步通信,
需要选择合适的消息队列技术。

## 决策
选择 Apache Kafka 作为消息队列。

## 考虑的方案

### 方案一:RabbitMQ
- 优点:成熟稳定,支持多种消息协议,管理界面友好
- 缺点:消息不持久化(默认),吞吐量相对较低

### 方案二:Apache Kafka
- 优点:高吞吐量,消息持久化,支持消息回放,生态丰富
- 缺点:运维复杂,学习曲线较高

### 方案三:AWS SQS
- 优点:全托管,无运维负担
- 缺点:供应商锁定,功能相对简单

## 结果
选择 Kafka,因为系统对吞吐量要求高,且需要消息回放能力
用于数据分析和故障恢复。

## 后果
- 需要团队学习 Kafka 运维知识
- 需要部署 Zookeeper(或使用 KRaft 模式)
- 获得了高吞吐量和消息持久化能力

第九章:架构评估方法

9.1 ATAM(架构权衡分析方法)

ATAM 是一种系统化的架构评估方法,通过识别架构决策与质量属性之间的权衡关系来评估架构。

主要步骤:

  1. 收集架构驱动因素(业务目标、质量属性场景)
  2. 描述架构
  3. 识别架构方法
  4. 生成质量属性效用树
  5. 分析架构方法
  6. 识别敏感点和权衡点
  7. 头脑风暴场景
  8. 分析结果

9.2 质量属性

在架构评估中,常见的质量属性包括:

质量属性 描述 常见度量指标
性能 系统响应时间和吞吐量 响应时间 P99、TPS
可用性 系统正常运行的时间比例 SLA(如 99.9%)
可扩展性 系统处理增长负载的能力 水平/垂直扩展能力
安全性 系统抵御攻击的能力 漏洞数量、合规性
可维护性 系统修改和演化的难易程度 代码复杂度、测试覆盖率
可测试性 系统验证行为的难易程度 单元测试覆盖率
可部署性 系统部署的频率和风险 部署频率、MTTR

第十章:总结与选型建议

10.1 架构模式对比

架构模式 复杂度 适用规模 主要优势 主要挑战
分层架构 小到中型 简单易懂 可能变得臃肿
微服务 中到大型 独立部署、技术多样性 分布式复杂性
事件驱动 中到高 中到大型 松耦合、高扩展性 最终一致性
六边形架构 任意规模 业务逻辑隔离 初期设计成本
Serverless 小到中型 无运维、弹性 冷启动、调试

10.2 选型建议

初创团队 / 小型项目

从单体架构或分层架构开始,不要过早引入微服务。等到团队规模增长、业务复杂度提升后,再考虑拆分。

中型团队 / 成长期产品

可以考虑模块化单体(Modular Monolith)作为过渡,保持代码内部的模块边界清晰,为未来拆分做准备。

大型团队 / 复杂业务

微服务架构配合 DDD 是较好的选择。但要注意:

  • 服务粒度不要过细
  • 建立完善的 DevOps 基础设施
  • 重视服务治理和可观测性

事件密集型系统

事件驱动架构非常适合需要处理大量异步事件的系统,如 IoT 数据处理、实时分析、通知系统等。

10.3 架构演进原则

  1. 从简单开始,按需演进
  2. 保持架构决策的可逆性
  3. 记录重要的架构决策(ADR)
  4. 定期进行架构评审
  5. 关注团队认知负荷,不要引入超出团队能力的复杂度
  6. 架构服务于业务,而不是为了技术而技术

本文档涵盖了主流软件架构模式的核心概念、优缺点和适用场景,供架构设计参考使用。


<
Previous Post
我花了一上午,帮一个 AI 找到了自己的”亲生父母”
>
Blog Archive
Archive of all previous blog posts