库存服务的端口与适配器实践
前置:理解请求标识、库存扣减及对象的可变状态;Python 3.10+ 用于单进程串行库存模型,不要求数据库或消息队列。教学范围为Parnas 1972信息隐藏;单进程端口适配;Python 3.10+。核对日期2026-10-17。示例未执行,本轮没有安装依赖、生成协议代码、启动服务或浏览器预览。完整源码和输入文件见下方「同源完整文件」;推演结论只适用于写明的模型。
完整库存端口示例
完整库存端口示例用MemoryStock维护库存与请求键,业务服务先校验数量再调用仓库预留。仓库集中保护不得负库存的约束,同一请求键同参数返回原结果,换参数则拒绝。本例仅串行内存模型,没有跨线程、进程或崩溃持久保证。
库存边界预期
库存边界预期从余量5开始:键r1预留3后余量2,同键同参数返回2而不再次扣减;键r2预留3拒绝,余量仍2。幂等键对应业务请求身份,不能每次重试换键,也不能把失败状态未经设计地永久缓存。
替换仓库练习
替换仓库练习要求把内存仓库换为关系数据库时写出哪些契约必须保留。接口签名相同不够:扣减与登记请求键需要同一事务的原子性,并发请求应靠约束或锁保护,连接失败后结果不确定也需可查询。
同源完整文件
以下README含前置、输入、读者自跑步骤、预期结果、失败、恢复与清理;只做静态核对,没有执行。
model.py
import json
import sys
class MemoryStock:
def __init__(self, amount):
if type(amount) is not int or amount < 0:
raise ValueError('nonnegative stock required')
self.amount = amount
self.requests = {}
def reserve(self, key, quantity):
if key in self.requests:
previous_quantity, result = self.requests[key]
if quantity != previous_quantity:
raise ValueError('idempotency key reused with different quantity')
return result
if quantity > self.amount:
raise ValueError('insufficient stock')
self.amount -= quantity
self.requests[key] = (quantity, self.amount)
return self.amount
def reserve(stock, key, quantity):
if not isinstance(key, str) or not key or type(quantity) is not int or quantity <= 0:
raise ValueError('nonempty key and positive quantity required')
return stock.reserve(key, quantity)
if __name__ == '__main__':
try:
data = json.load(sys.stdin)
stock = MemoryStock(data['stock'])
results = []
for request in data['requests']:
try:
results.append({'remaining': reserve(stock, request['key'], request['quantity'])})
except ValueError as error:
results.append({'error': str(error)})
print(json.dumps({'results': results, 'stock': stock.amount}))
except (ValueError, TypeError, KeyError) as error:
print(str(error), file=sys.stderr)
sys.exit(2)requirements.txt
# Python 3.10+ standard library only; no third-party dependency.README.md
# 架构风格与模块边界:完整示例
示例未执行。核对日期2026-10-17。教学版本族:Parnas 1972信息隐藏;单进程端口适配;Python 3.10+。
## 文件与依赖
文件:model.py, requirements.txt。requirements.txt为同源依赖声明;所有代码和配置都随本文展示。本例使用 Python 3.10+ 标准库,无额外运行依赖。
## 输入与前置
前置:理解库存余量、请求键和同一请求重试。使用 Python 3.10+,只依赖标准库;仓库是单进程内存对象,没有数据库或消息队列。输入包含初始 stock 和按顺序处理的 requests,具体输入与预期见下节。
读者将来自跑(本轮未执行):在本目录以Python 3.10+读取input.json,PowerShell示意`Get-Content -Raw input.json | python model.py`;Bash示意`python3 model.py < input.json`。把下面输入手工保存为UTF-8文件,输出按预期逐项比较。无需安装依赖,没有服务或网络调用;程序只读标准输入并写标准输出,不改原文件。失败信息写标准错误且退出2。恢复是修正输入后重读,清理只删除自己创建的input.json与输出文件,勿递归删除工作目录。
## 预期结果、失败与恢复
输入 {"stock":5,"requests":[{"key":"r1","quantity":3},{"key":"r1","quantity":3},{"key":"r2","quantity":3}]};预期remaining2、remaining2、insufficient stock;最终stock=2。
失败要保留输入和错误,先与正文的不变量及边界核对。没有真实服务或性能验证,不以这些模型输出证明生产可用性。
## 独立练习
给数据库版本设计一条并发情景:两个不同键各预留3,初始5;再给相同键两次请求的提交约束。
参考答案:不同键竞争同一库存行,只有一个能在检查余量后扣减成功,另一个读到2并拒绝;检查与扣减不能拆事务。同键需要唯一键加事务登记,冲突后核对参数再读原结果,不能先提交库存再写幂等记录。先作答,再核对
给数据库版本设计一条并发情景:两个不同键各预留3,初始5;再给相同键两次请求的提交约束。
逐步参考解答
不同键竞争同一库存行,只有一个能在检查余量后扣减成功,另一个读到2并拒绝;检查与扣减不能拆事务。同键需要唯一键加事务登记,冲突后核对参数再读原结果,不能先提交库存再写幂等记录。
请求键与库存必须一起改变
r1 的第一次调用扣减3,重试读取已保存结果,不再扣减。若同键换成数量4,应拒绝参数冲突,不能返回貌似成功的旧结果。图示为串行教学路径;数据库版本需把请求键检查、扣减和结果登记放在能抵御并发冲突的事务中。
查看图表代码
来源与适用边界
- 英文官方、作者或规范原文:本专题机制依据;按上述固定版本族阅读。
- 中文官方或作者资料:用于术语和讲解交叉核对,冲突以原始规范为准。
继续阅读
- 架构取舍与原文
- 模块与交互决策
- 架构取舍与原文