在上一章中,我们介绍了行为型模式——它们帮助我们处理对象之间的互联与算法。本章要讨论的下一类是架构型设计模式。这些模式为解决常见的架构问题提供了模板,便于构建可扩展、可维护、可复用的系统。
本章将涵盖以下主要主题:
- 模型–视图–控制器(MVC)模式
- **微服务(Microservices)**模式
- **无服务器(Serverless)**模式
- **事件溯源(Event Sourcing)**模式
- 其他架构型设计模式
在本章结束时,你将理解如何使用流行的架构型设计模式来构建健壮且灵活的软件。
技术要求
参见第 1 章给出的通用要求。本章相关代码的附加技术要求如下:
微服务模式一节需要安装:
- gRPC:
python -m pip install grpcio - gRPC-tools:
python -m pip install grpcio-tools - Lanarky 及其依赖:
python -m pip install "lanarky[openai]"==0.8.6 uvicorn==0.29.0
(注意:撰写时与 Python 3.12 不兼容;可使用 Python 3.11 复现相关示例。)
无服务器模式一节需要安装:
- Docker
- LocalStack(用于本地测试 AWS Lambda):
python –m pip install localstack
(注意:撰写时与 Python 3.12 不兼容;此处可使用 Python 3.11。) - awscli-local:
python -m pip install awscli-local - awscli:
python -m pip install awscli
事件溯源一节需要安装:
- eventsourcing:
python –m pip install eventsourcing
MVC 模式
MVC 模式是松耦合原则的又一应用。名称来自将应用拆分为三个主要组件:模型(Model) 、视图(View) 、控制器(Controller) 。
即使我们很少从零实现 MVC,也需要熟悉它,因为主流框架都使用 MVC 或其变体(稍后会提到)。
- 模型(Model) 是核心,代表“知识”。它包含并管理应用的(业务)逻辑、数据、状态与规则。
- 视图(View) 是模型的可视化呈现:如桌面 GUI、终端文本输出、手机 App 界面、PDF、饼图、柱状图等。视图只显示数据,不处理数据。
- 控制器(Controller) 是模型与视图之间的链接/粘合剂。模型与视图之间的所有通信都经由控制器进行。
一个使用 MVC 的应用(在初始屏幕渲染后)通常按以下流程工作:
- 用户通过点击/输入/触摸等触发视图。
- 视图告知控制器用户的动作。
- 控制器处理输入并与模型交互。
- 模型完成必要的校验与状态变更,并告知控制器该做什么。
- 控制器指示视图更新并按模型的指示展示输出。
图 6.1 – MVC 模式
控制器是否一定需要?
理论上可以省略,但会失去 MVC 的一个重要收益:在不修改模型的前提下支持多个视图(甚至同时) 。为实现模型与其表示的解耦,通常每个视图需要自己的控制器。若模型直接与特定视图通信,将很难(或至少不够干净、模块化地)支持多视图。
现实世界示例
MVC 体现的是关注点分离(Separation of Concerns) 。生活中大量使用该原则:
- 盖房子时,通常分别由不同专业人员进行水电安装与粉刷。
- 餐厅中,服务员负责点单与上菜,而厨师负责烹饪。
在 Web 开发中,多个框架采用 MVC 思想,例如:
- Web2py:轻量级 Python 框架,拥抱 MVC。项目网站(web2py.com/)与 GitHub 上有大量示例。
- Django:本质上也是 MVC,但命名不同——称为 MVT(Model-View-Template) 。在 Django 中,controller 叫 view,而 view 叫 template。Django 的设计者认为,view 描述用户“看到哪些数据”,因此以 Python 回调函数(与 URL 绑定)命名为 view;template 用于内容与表现分离,描述“数据如何被看到”,而非“看到哪些数据”。
MVC 的适用场景
MVC 十分通用实用。几乎所有流行的 Web 框架(Django、Rails、Symfony、Yii)与应用框架(iPhone SDK、Android、QT)都使用 MVC 或其变体(如 MVA、MVP、MVT)。即便不使用框架,自行实现 MVC 也很有意义,收益包括:
- 视图与模型分离:设计师专注 UI,程序员专注开发,互不干扰。
- 松耦合:任一方可独立修改/扩展而不影响另一方。例如,新增一个视图非常容易——为其实现一个控制器即可。
- 职责清晰,便于维护。
从零实现 MVC 时,请确保做到:聪明的模型(Smart Models) 、瘦控制器(Thin Controllers) 、笨视图(Dumb Views) 。
Smart Model(聪明的模型) :
- 包含所有校验/业务规则/业务逻辑
- 处理应用状态
- 访问应用数据(数据库、云等)
- 不依赖 UI
Thin Controller(瘦控制器) :
- 当用户与视图交互时,更新模型
- 当模型变化时,更新视图
- 必要时对数据做预处理再交给模型/视图
- 不直接显示数据
- 不直接访问应用数据
- 不包含校验/业务规则/逻辑
Dumb View(笨视图) :
- 展示数据
- 允许用户与其交互
- 仅做极少量处理(通常由模板语言提供,如变量与循环)
- 不存储数据
- 不直接访问应用数据
- 不包含校验/业务规则/逻辑
自检你的 MVC 实现是否到位,可思考:
- 若应用有 GUI,是否易于换肤(skin) ?能否在运行时切换皮肤?若不容易,说明实现可能存在问题。
- 若当前无 GUI(如终端应用),添加 GUI 的难度如何?或者,是否容易新增视图以图表(饼图、柱状图)或文档(PDF、表格)方式展示结果?若这些更改并非“只需为新视图创建一个控制器、而无需修改模型”,则 MVC 未正确实现。
满足这些条件,应用的灵活性与可维护性将显著优于未使用 MVC 的实现。
MVC 模式的实现
为完整说明 MVC,我们不依赖任何框架,而是从零实现一个极简示例: “名言打印机” 。思路:用户输入一个数字,程序显示对应的名言。名言保存在 quotes 元组中(现实中通常在数据库或文件里,且只有模型能直接访问)。
示例 quotes 元组如下:
quotes = (
"A man is not complete until he is married. Then he is finished.",
"As I said before, I never repeat myself.",
"Behind a successful man is an exhausted woman.",
"Black holes really suck...",
"Facts are stubborn things.",
)
模型极简:只有 get_quote() 方法,按索引 n 返回对应字符串:
class QuoteModel:
def get_quote(self, n):
try:
value = quotes[n]
except IndexError as err:
value = "Not found!"
return value
视图有三个方法:
show():在屏幕上打印名言或“Not found!”error():打印错误信息select_quote():读取用户选择
class QuoteTerminalView:
def show(self, quote):
print(f'And the quote is: "{quote}"')
def error(self, msg):
print(f"Error: {msg}")
def select_quote(self):
return input("Which quote number would you like to see? ")
控制器负责协调:__init__() 初始化模型与视图;run() 校验用户输入的索引,从模型取回名言并交给视图显示:
class QuoteTerminalController:
def __init__(self):
self.model = QuoteModel()
self.view = QuoteTerminalView()
def run(self):
valid_input = False
while not valid_input:
try:
n = self.view.select_quote()
n = int(n)
valid_input = True
except ValueError as err:
self.view.error(f"Incorrect index '{n}'")
quote = self.model.get_quote(n)
self.view.show(quote)
最后,main() 初始化并启动控制器:
def main():
controller = QuoteTerminalController()
while True:
controller.run()
示例回顾(完整代码见 ch06/mvc.py):
- 定义名言列表变量;
- 定义模型类
QuoteModel; - 定义视图类
QuoteTerminalView; - 定义控制器类
QuoteTerminalController; - 编写
main()测试,并按惯例调用。
运行 python ch06/mvc.py 的示例输出:
Which quote number would you like to see? 3
And the quote is: "Black holes really suck..."
Which quote number would you like to see? 2
And the quote is: "Behind a successful man is an exhausted woman."
Which quote number would you like to see? 6
And the quote is: "Not found!"
Which quote number would you like to see? 4
And the quote is: "Facts are stubborn things."
Which quote number would you like to see? 3
And the quote is: "Black holes really suck..."
Which quote number would you like to see? 1
And the quote is: "As I said before, I never repeat myself."
微服务(Microservices)模式
传统上,开发者在构建服务端应用时,通常使用单一代码库,在其中实现全部或大部分功能,配合我们在本书前面介绍过的函数、类以及各种设计模式来组织代码。
然而,随着 IT 行业的发展、经济因素的影响,以及对更快上市和投资回报的压力,工程团队需要持续改进实践,以在服务器、服务交付与运维方面获得更强的响应性与可扩展性。这意味着我们需要了解除面向对象编程(OOP)之外的更多有用模式。
图 6.2 – 微服务模式
近年来工程师常用模式目录中最重要的新增成员之一,就是微服务架构(Microservice Architecture,简称微服务)。其思想是:把应用构建为一组松耦合、可协作的服务。在这种架构风格下,一个应用可能由订单管理服务、客户管理服务等多个服务组成。它们松耦合、可独立部署,并通过定义良好的 API 进行通信。
现实世界示例
例如:
- Netflix:最早采用微服务以同时处理数以百万计的内容流。
- Uber:用微服务分别处理计费、通知、行程跟踪等不同职能。
- Amazon:从单体架构迁移到微服务以支撑持续增长的规模。
适用场景
当你的应用具备以下一项或多项特征时,微服务往往是聪明的选择:
- 需要同时支持桌面与移动端等不同客户端
- 需要对外提供第三方可用的 API
- 必须通过消息与其他应用通信
- 处理请求时需要访问数据库、与其他系统通信,并返回 JSON/XML/HTML/PDF 等不同类型的响应
- 存在按功能领域划分的逻辑组件
实现微服务模式——使用 gRPC 的支付服务
先简谈微服务世界里的安装与部署。从“部署一个单体应用”切换到“部署许多小服务”,需要处理的事项会指数级增长。以往也许一个应用服务器加少量运行时依赖就够用;到了微服务时代,依赖会大幅增加:某个服务需要关系型数据库,另一个需要 ElasticSearch;这边用 MySQL,那边用 Redis……因此,采用微服务通常也意味着要使用容器。
得益于 Docker,我们可以把“应用服务器、依赖与运行库、编译产物、配置”等一并打包到容器里,然后启动这些容器化服务,并确保它们之间能够通信。
当然,你也可以直接用 Django/Flask/FastAPI 来实现 Web 或 API 的微服务。为了快速给出可运行示例,这里采用 gRPC ——一个高性能的通用 RPC 框架,使用 Protocol Buffers(protobuf) 作为接口描述语言,跨语言、效率高,非常适合微服务间通信。
设想一个架构,其中有一个专门处理支付的微服务(称为 PaymentService),负责处理支付,并与 OrderService、AccountService 等其他服务交互。我们将聚焦于使用 gRPC 实现该支付服务。
首先,在 ch06/microservices/grpc/payment.proto 中用 protobuf 定义服务与方法(包括请求与响应消息):
syntax = "proto3";
package payment;
// The payment service definition.
service PaymentService {
// Processes a payment
rpc ProcessPayment (PaymentRequest) returns (PaymentResponse) {}
}
// The request message containing payment details.
message PaymentRequest {
string order_id = 1;
double amount = 2;
string currency = 3;
string user_id = 4;
}
// The response message containing the result of the payment process.
message PaymentResponse {
string payment_id = 1;
string status = 2; // e.g., "SUCCESS", "FAILED"
}
然后使用 protoc 把 payment.proto 编译为 Python 代码。一般命令形式如下:
python -m grpc_tools.protoc -I<PROTO_DIR> --python_out=<OUTPUT_DIR> --grpc_python_out=<OUTPUT_DIR> <PROTO_FILES>
在本例中,切到正确目录(如 cd ch06/microservices/grpc),执行:
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. payment.proto
该命令将在当前目录生成两份文件:payment_pb2.py 与 payment_pb2_grpc.py(不要手动修改它们)。
接着在 payment_service.py 中编写服务逻辑,继承生成文件中的基类并实现方法。例如:
from concurrent.futures import ThreadPoolExecutor
import grpc
import payment_pb2
import payment_pb2_grpc
class PaymentServiceImpl(payment_pb2_grpc.PaymentServiceServicer):
def ProcessPayment(self, request, context):
return payment_pb2.PaymentResponse(payment_id="12345", status="SUCCESS")
用 grpc.server(ThreadPoolExecutor(max_workers=10)) 启动服务:
def main():
print("Payment Processing Service ready!")
server = grpc.server(ThreadPoolExecutor(max_workers=10))
payment_pb2_grpc.add_PaymentServiceServicer_to_server(PaymentServiceImpl(), server)
server.add_insecure_port("[::]:50051")
server.start()
server.wait_for_termination()
写一个客户端(ch06/microservices/grpc/client.py)来调用服务:
import grpc
import payment_pb2
import payment_pb2_grpc
with grpc.insecure_channel("localhost:50051") as chan:
stub = payment_pb2_grpc.PaymentServiceStub(chan)
resp = stub.ProcessPayment(
payment_pb2.PaymentRequest(
order_id="order123",
amount=99.99,
currency="USD",
user_id="user456",
)
)
print("Payment Service responded.")
print(f"Response status: {resp.status}")
启动服务(ch06/microservices/grpc/payment_service.py):
python ch06/microservices/grpc/payment_service.py
输出应类似:
Payment Processing Service ready!
另开一个终端运行客户端(ch06/microservices/grpc/client.py):
python ch06/microservices/grpc/client.py
你应看到:
Payment Service responded.
Response status: SUCCESS
这正是预期结果。
虽然 gRPC 是微服务通信的强力之选,但在更强调可读性或Web 集成的场景下,基于 HTTP 的 REST 也完全可用。gRPC 的优势在于性能与对流式请求/响应的支持,本例用它来做一个简明介绍。
实现微服务模式——使用 Lanarky 的 LLM 服务
Lanarky 基于 FastAPI,为构建使用**大语言模型(LLM)**的微服务提供“电池全备”的体验。我们按照其官网 lanarky.ajndkr.com 的 Getting started 步骤,展示一个由 Lanarky 驱动的微服务。
要测试该示例,需要设置环境变量 OPENAI_API_KEY 以使用 OpenAI。访问 openai.com 获取 API Key。
服务端示例代码(节选):
import os
import uvicorn
from lanarky import Lanarky
from lanarky.adapters.openai.resources import ChatCompletionResource
from lanarky.adapters.openai.routing import OpenAIAPIRouter
# 传入 OpenAI API Key(示例仅为演示)
os.environ["OPENAI_API_KEY"] = "Your OpenAI API key here"
安全实践
建议在shell 环境变量里设置密钥,再由代码读取,不要把密钥硬编码在源码中。
创建应用与路由(与 FastAPI 约定一致),Lanarky 提供 OpenAIAPIRouter:
app = Lanarky()
router = OpenAIAPIRouter()
定义 /chat 的 POST 处理函数:
@router.post("/chat")
def chat(stream: bool = True) -> ChatCompletionResource:
system = "Here is your assistant"
return ChatCompletionResource(stream=stream, system=system)
把路由挂到应用并用 uvicorn 启动:
if __name__ == "__main__":
app.include_router(router)
uvicorn.run(app)
编写客户端与服务交互,例如:
import click
import sys
from lanarky.clients import StreamingClient
args = sys.argv[1:]
if len(args) == 1:
message = args[0]
client = StreamingClient()
for event in client.stream_response(
"POST",
"/chat",
params={"stream": "false"},
json={"messages": [dict(role="user", content=message)]},
):
print(f"{event.event}: {event.data}")
else:
print("You need to pass a message!")
测试方式与前面 gRPC 示例类似。首先在一个终端启动 LLM 服务(ch06/microservices/lanarky/llm_service.py):
python ch06/microservices/lanarky/llm_service.py
你应看到类似输出:
INFO: Started server process [18617]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
另开终端运行客户端:
python ch06/microservices/lanarky/client.py "Hello"
输出示例:
completion: Hello! How can I assist you today?
继续发送消息,服务会返回补全结果,如同在 ChatGPT 界面中交互。例如:
python ch06/microservices/lanarky/client.py "What is the capital of Switzerland?"
completion: The capital of Switzerland is Bern.
无服务器(Serverless)模式
Serverless 模式将服务器管理抽象掉,让开发者只需专注于编写代码。云服务商根据事件触发(例如 HTTP 请求、文件上传或数据库修改)来负责扩缩容与执行。
图 6.3 —— 无服务器(Serverless)模式
Serverless 尤其适用于微服务、API 与事件驱动架构。
现实世界示例
我们可以想到多种 Serverless 的应用场景,例如:
- 自动数据备份:定时触发的无服务器函数将重要数据自动备份到云存储
- 图片处理:用户上传图片后,函数自动进行裁剪、压缩或滤镜处理
- 电商收据 PDF 生成:交易完成后,函数生成 PDF 收据并通过邮件发送给客户
适用场景
Serverless 主要适用于两类架构:
- 事件驱动架构:当事件发生时执行特定函数,例如图片处理(裁剪、缩放)或动态 PDF 生成。
- 微服务架构:每个微服务都可以做成一个无服务器函数,便于管理与横向扩展。
上一节我们已经讨论了微服务,本节聚焦第一类用法的实现方式。
实现 Serverless 模式
下面以 AWS Lambda 为例,演示一个对数字求平方的最小函数。AWS Lambda 是亚马逊的无服务器计算服务,可在数据变化、系统状态改变或用户动作等触发下运行代码。
我们不额外增加业务复杂度,专注于 Serverless 架构与 AWS Lambda 的部署要点。
首先,编写 Python 函数。创建 lambda_handler(),接收 event 与 context 两个参数。这里我们从 event 字典的 "number" 键读取输入,计算平方并返回结果字符串:
import json
def lambda_handler(event, context):
number = event["number"]
squared = number * number
return f"The square of {number} is {squared}."
有了函数后,需要将其部署为 AWS Lambda。为了学习与本地快速测试,我们不直接部署到真·AWS,而是使用 LocalStack 在本地模拟 AWS 环境。
安装好 LocalStack 后,在你的环境中以 Docker 容器方式启动它(localstack 可执行文件安装自 Python 环境):
localstack start -d
将 Python 源文件(ch06/lambda_function_square.py)打成 ZIP 包,例如:
zip lambda.zip lambda_function_square.py
我们还需要 awslocal(需单独安装)。使用它把 Lambda 函数部署到“本地 AWS”基础设施中。命令如下:
awslocal lambda create-function \
--function-name lambda_function_square \
--runtime python3.11 \
--zip-file fileb://lambda.zip \
--handler lambda_function_square.lambda_handler \
--role arn:aws:iam::000000000000:role/lambda-role
请按你的 Python 版本调整
--runtime
上述示例在撰写时使用 Python 3.11。请根据你的环境修改此参数。
使用 payload.json 提供输入,调用该 Lambda 函数:
awslocal lambda invoke --function-name lambda_function_square \
--payload file://payload.json output.txt
随后查看 output.txt 的内容,应该能看到:
The square of 6 is 36.
这是本地的初步测试。我们还可以为该函数创建一个 函数 URL。同样用 awslocal:
awslocal lambda create-function-url-config \
--function-name lambda_function_square \
--auth-type NONE
该命令会生成一个用于调用 Lambda 的 URL,形如:
http://<XXXXXXXX>.lambda-url.us-east-1.localhost.localstack.cloud:4566
现在就可以用 cURL 触发这个 URL 了,例如:
curl -X POST \
'http://iu4s187onr1oabg50dbvm77bk6r5sunk.lambda-url.us-east-1.localhost.localstack.cloud:4566/' \
-H 'Content-Type: application/json' \
-d '{"number": 6}'
关于 AWS Lambda 的最新与详细指南,请参考官方文档:docs.aws.amazon.com/lambda/。
以上是一个极简示例。另一个典型的 Serverless 应用,是生成业务收据 PDF 的函数:企业无需管理服务器,只为实际消耗的计算时间付费。
事件溯源(Event Sourcing)模式
事件溯源模式将状态变更保存为一系列事件,从而可以重放这些事件来重建任意历史时刻的状态,并提供可审计的变更轨迹。该模式特别适用于状态复杂且业务规则(状态迁移)复杂的系统。
正如稍后实现示例所示,事件溯源强调:对应用状态的所有更改都要捕获为有序事件。其结果是:只需回放这些事件,就能在任意时间点重建应用状态。
现实世界示例
在软件领域有不少实例:
- 审计追踪:为合规而记录对数据库所做的全部更改
- 协同编辑:允许多个用户同时编辑同一文档
- 撤销/重做:为应用中的操作提供撤销与重做能力
适用场景
典型用例包括(举三例):
- 金融交易:将账户余额的每一次变更记录为时间顺序上的不可变事件(存款、取款、转账均为独立事件),从而形成透明、可审计且安全的账本。
- 库存管理:以事件记录每件物品生命周期中的每次变更,保持库存数据准确、可追溯,并有助于识别销量/使用模式与预测需求;在召回或质量调查时可追根溯源。
- 用户行为追踪:将用户的每次交互(浏览、加购、下单、退货等)记录为事件,以便进行行为分析、个性化营销、体验优化与推荐改进。
下面看看如何实现该模式。
实现事件溯源模式——手工方式
先给出一些定义:
- Event(事件) :对状态变更的表示,通常包含事件类型与相关数据。事件一旦创建并应用,就不可更改。
- Aggregate(聚合) :代表统一业务逻辑或数据边界的对象(或对象簇)。它维护自身的状态,每次发生变更(事件)时记录该事件。
- Event store(事件存储) :保存系统中发生的全部事件的集合。
通过“事件驱动”的方式处理状态变更,可以让业务逻辑更灵活、易扩展:新增事件类型或调整既有事件的处理都能将影响面降到最低。
在这个银行账户的示例中,我们手工实现事件溯源:定义事件数据结构并手写事件应用逻辑。
我们先定义 Account 类,表示带有余额与事件列表的银行账户。该类充当聚合,其 events 属性同时扮演事件存储。这里的事件用字典表示,包含操作类型("deposited" 或 "withdrawn")与金额:
class Account:
def __init__(self):
self.balance = 0
self.events = []
def apply_event(self, event):
if event["type"] == "deposited":
self.balance += event["amount"]
elif event["type"] == "withdrawn":
self.balance -= event["amount"]
self.events.append(event)
接着添加存取款方法,内部均调用 apply_event():
def deposit(self, amount):
event = {"type": "deposited", "amount": amount}
self.apply_event(event)
def withdraw(self, amount):
event = {"type": "withdrawn", "amount": amount}
self.apply_event(event)
最后是用于测试的 main():
def main():
account = Account()
account.deposit(100)
account.deposit(50)
account.withdraw(30)
account.deposit(30)
for evt in account.events:
print(evt)
print(f"Balance: {account.balance}")
运行(python ch06/ event_sourcing/bankaccount.py)输出示例:
{'type': 'deposited', 'amount': 100}
{'type': 'deposited', 'amount': 50}
{'type': 'withdrawn', 'amount': 30}
{'type': 'deposited', 'amount': 30}
Balance: 150
这个例子用最简方式帮你建立了对事件溯源的直观理解。更复杂的系统可使用专门的库来管理事件存储、查询与处理。下面试试一个库。
实现事件溯源模式——使用库
第二个例子我们使用 eventsourcing 库来实现一个库存管理系统,跟踪商品数量。
首先导入需要的内容:
from eventsourcing.domain import Aggregate, event
from eventsourcing.application import Application
定义聚合 InventoryItem,继承 Aggregate,并用 @event 装饰器声明会产生事件的方法:
class InventoryItem(Aggregate):
@event("ItemCreated")
def __init__(self, name, quantity=0):
self.name = name
self.quantity = quantity
@event("QuantityIncreased")
def increase_quantity(self, amount):
self.quantity += amount
@event("QuantityDecreased")
def decrease_quantity(self, amount):
self.quantity -= amount
再定义应用层 InventoryApp,继承 Application。save() 会收集聚合上未提交的事件并写入应用的事件存储:
class InventoryApp(Application):
def create_item(self, name, quantity):
item = InventoryItem(name, quantity)
self.save(item)
return item.id
增加数量增/减的方法:先通过仓库取回聚合,调用其行为方法,然后 save():
def increase_item_quantity(self, item_id, amount):
item = self.repository.get(item_id)
item.increase_quantity(amount)
self.save(item)
def decrease_item_quantity(self, item_id, amount):
item = self.repository.get(item_id)
item.decrease_quantity(amount)
self.save(item)
测试代码如下:
def main():
app = InventoryApp()
# Create a new item
item_id = app.create_item("Laptop", 10)
# Increase quantity
app.increase_item_quantity(item_id, 5)
# Decrease quantity
app.decrease_item_quantity(item_id, 3)
notifs = app.notification_log.select(start=1, limit=5)
notifs = [notif.state for notif in notifs]
for notif in notifs:
print(notif.decode())
运行(python ch06/ event_sourcing/inventory.py)输出示例:
{"timestamp":{"_type_":"datetime_iso","_data_":"2024-03-18T08:05:10.583875+00:00"},"originator_topic":"__main__:InventoryItem","name":"Laptop","quantity":10}
{"timestamp":{"_type_":"datetime_iso","_data_":"2024-03-18T08:05:10.584818+00:00"},"amount":5}
{"timestamp":{"_type_":"datetime_iso","_data_":"2024-03-18T08:05:10.585128+00:00"},"amount":3}
很好!这个例子与前一个一起展示了事件溯源式应用的设计思路。对于更有野心的项目,可以借助 eventsourcing 等库,降低事件存储与重放等基础设施的实现复杂度。
其他架构设计模式
你还可能遇到下列架构模式:
- 事件驱动架构(EDA) :强调事件的产生、检测、消费与响应,具有高度适应性与可扩展性,适用于需要对重要事件实时反应的环境。
- 命令查询职责分离(CQRS) :将“写入(命令)”模型与“读取(查询)”模型分离,尤其在读写关注点差异明显时,有助于扩展性与可维护性。
- 整洁架构(Clean Architecture) :通过依赖反转等原则,将业务逻辑与对外接口解耦,强调以领域为中心来组织代码。
小结
本章我们探讨了若干关键的架构设计模式,它们在现代软件开发中各有侧重、各解难题:
- MVC:通过将应用划分为模型、视图与控制器三部分,促进关注点分离,使代码更易管理、扩展与测试。
- 微服务:将应用拆分为小而独立的服务,每个服务负责单一业务能力,提升伸缩性、灵活性与部署效率,适合复杂且快速演进的业务。
- 无服务器(Serverless) :把重心从服务器管理转向业务逻辑,借助云服务按事件执行代码,降低成本、自动扩缩容并提升生产力。
- 事件溯源(Event Sourcing) :把每次数据变更记录为事件序列,既提供强大的审计追踪与复杂业务能力,又能重建历史状态、洞察数据演进。
- 其他模式(如 CQRS、整洁架构):进一步丰富工程师的架构工具箱,帮助构建结构良好、可维护的系统。
在下一章中,我们将讨论并发与异步模式与技术,帮助程序同时管理多项操作,或在等待耗时任务时继续处理其他工作。