分层之后,为什么每一层还在重复校验?
一个修改商品价格的功能,接口检查价格是不是整数,应用服务检查一次,领域对象检查一次,保存时又检查一次。代码分成了四层,同一个问题也被问了四遍。 这些检查看起来都在提高可靠性,但如果删掉其中一条,我们能说清楚究竟失去了什么保护吗? 之前的《让领域模型和仓储模型各自负责》讨论了模型与持久化之间的边界。这篇继续讨论一个更具体的问题:边界已经划分之后,哪些检查应该保留,哪些可以交给前面的代码保证? 如果你已经接触过 Python 的 dataclass 和简单分层,但觉得业务代码越来越像校验框架,可以从下面这个改价功能开始。 一个价格,为什么检查四遍 假设商品后台收到这样的 JSON: { "sku": "P1001", "price_cents": 9900 } 价格使用整数分。对于这个例子,业务还约定:价格不能为负数,停售商品不能改价。 容易写出的代码是,在入口、服务、模型和 Repository 中都放上类似判断: if type(price_cents) is not int: raise ValueError("价格必须是整数分") 问题在于,这四次检查通常处理的是同一个已经解析完成的值。只要内部代码没有重新引入不可信数据,重复检查提供的保护就很有限。 真正需要区分的是两个问题: 问题 例子 负责的位置 输入是否符合接口约定? 缺少价格、价格是字符串 外部输入入口 操作是否符合业务规则? 负价格、对停售商品改价 领域模型 应用服务负责组织操作:加载商品、调用改价方法、保存商品。Repository 负责读写和映射。它们不需要把前两类判断各自重写一份。 先建立一个可以信任的入口 Python 的类型注解不会自动验证运行时数据。写下 price_cents: int,并不能阻止调用方传入字符串。 因此,“内部可以信任类型”需要前提:外部数据进入内部流程之前,确实经过了解析和检查。 下面是这个入口。示例采用严格协议,只接受两个字段;不自动把字符串价格转换成整数。 from dataclasses import dataclass, replace @dataclass(frozen=True) class ChangePriceRequest: sku: str price_cents: int def decode_change_price(data: object) -> ChangePriceRequest: if not isinstance(data, dict): raise ValueError("请求必须是 JSON 对象") if set(data) != {"sku", "price_cents"}: raise ValueError("请求必须且只能包含 sku 和 price_cents") sku = data["sku"] if not isinstance(sku, str) or not sku.strip(): raise ValueError("sku 必须是非空字符串") if type(data["price_cents"]) is not int: raise ValueError("价格必须是整数分") return ChangePriceRequest(sku.strip(), data["price_cents"]) 这里使用 type(value) is int,是因为 Python 的 bool 是 int 的子类。这个接口不接受 true 作为价格。 ...