← [ BACK_TO_NOTES ]

防腐层哪里使用

按模型差异与维护成本判断防腐层适用范围,区分领域翻译、适配器、Repository 与普通工具包装。

#howto #evergreen #dev

[!info] related notes

防腐层哪里使用

先找出两边模型的差异,再判断是否需要防腐层。外部依赖存在,并不足以说明应该增加一层封装。

判断顺序

  1. 外部状态、单位、规则是否与内部业务含义不同?
  2. 这些差异是否已经散落到业务代码中,或会随供应商变化反复修改?
  3. 能否定义一个内部确实需要的接口,并完整表达成功、失败和不确定结果?
  4. 转换与维护的收益是否高于新增代码、延迟和运维成本?

六类场景

场景适用做法与边界
第三方支付、短信、存储等将供应商模型翻译为业务需要的能力与结果;仅改接口名字不代表已隔离语义
基础工具库简单包装可减少导入耦合,但通常只是 adapter 或 wrapper;不要为所有库建立万能工具类
持久化Repository / Mapper 可隔离存储表示;事务、一致性和查询能力仍可能影响业务,不保证数据库可无成本替换
限界上下文集成用消费方模型理解生产方的数据和事件,避免直接共享整个领域实体
前端数据适配将后端状态码、单位等转换成前端可用模型;纯显示格式与本地化可留在视图层,不必全放进领域实体
遗留系统迁移在新旧模型间转换;可配合渐进替换的 Strangler Fig 策略,但防腐层和迁移策略不是同一种模式

支付例子:别把创建意图当成扣款

旧示例将 stripe.paymentIntents.create() 包装为 charge(): Promise<string>。它隐藏了需要用户操作、处理中、失败等状态,拿到 ID 并不证明扣款成功。

内部接口应先确定业务动作是“发起支付”还是“查询已确认结果”,然后映射供应商状态、金额、币种与错误。还要明确幂等键和超时后的查询方式,不能靠接口名称制造成功语义。

工具替换仍需检查契约

  • 将 Moment 换成 Day.js、Luxon 或 Temporal 时,要检查时区、可变性、日期运算和无效输入行为;Moment 处于维护模式,不应简单写成“停止维护”。
  • UUID 与 Nano ID 的格式、长度、唯一性假设和兼容协议不同,type Uuid = string 不会提供运行时校验,也不能让二者自动等价。
  • 时钟接口可用于固定测试时间;没有这种需求时,不必仅为未来可能换库增加静态 DateUtils。

上下文映射例子

用户系统可能提供账号资料,订单系统只需要客户标识与收货信息。应按订单语义决定是引用当前地址,还是保存下单时地址快照。

仅把 UserDTO.address 复制为 Customer.shippingAddress 还不能证明模型正确;要确认地址归属、有效期和缺失情况。不要因为字段叫 Customer 就默认它一定是值对象。

如何验证

用实际契约样例验证状态、缺字段、未知值、单位和错误映射;必要时增加供应商契约测试。对无法可靠转换的输入显式报错或进入待处理状态,不默默丢失含义。

即使简单 CRUD 不需要完整 ACL,也仍需控制响应字段、权限和外部输入,不能把数据库记录不加区分地暴露给客户端。

创建于 2026/1/27 更新于 2026/10/6
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本