业务扩展框架01:TMF与美团平台化实践解决什么问题
多业务共用平台以后,复用为什么会变难
一个订单平台同时支持普通零售、预售和团购。三者都有商品、价格、订单与库存,但预售需要校验销售时间和预售额度,团购需要校验成团条件,普通零售通常直接处理现货。把共同步骤提取出来并不难,难的是下一次改预售规则时,怎样确定团购完全不受影响。
最直接的写法是在每个步骤判断业务类型。业务增多后,条件会散落到价格、库存、退款、消息消费等位置。新增业务的代价不只是多写几个分支,还包括找到所有分支、理解组合顺序、协调多个团队发布,以及证明旧业务没有改变。
将分支改成若干 Strategy 类,只解决了代码摆放。若每个策略自行判断是否适用、彼此没有清晰归属,两个策略可能同时生效,运行时仍然需要回答:这次请求应执行谁,为什么,冲突时怎么办。
TMF 一类业务扩展框架要把这些关系显式化:平台定义稳定的业务契约和变化点,业务提供定制实现,配置表达安装关系与冲突决策,运行时按明确的业务上下文执行。它需要的前提是已经存在值得复用的业务模型;框架不会从一堆任意 Java 方法中自动发现一个交易平台。
本系列分为三篇:
- 本篇:TMF 与美团公开实践的目标、领域模型和架构边界。
- 第二篇:注解究竟描述什么,静态元数据怎样与动态配置协作。
- 第三篇:运行 Java mock,并用 Lattice、OCTO、ServiceComb 检验远程绑定。
已有的 《插件化架构》 可以作为概念索引。本系列中的厂商事实以公开原始资料为准,教学模型与代码单独标明。
TMF、BPF与公开证据的边界
TMF 通常指阿里交易模块化框架 Trade Modularization Framework。2018 年的公开介绍强调业务插件与平台分离、统一业务身份,以及管理域和运行域分离;2021 年设计者毗卢的文章进一步解释这些设计如何处理业务规则归属与协作问题。文章描述的是当时的架构,不能直接当成 2026 年阿里内部的现状。阿里技术:TMF2.0 介绍、毗卢:TMF2.0 的关键设计要点。
“美团 BPF”在部分二手文章里与 TMF 并列,但截至 2026-10-15,本次检索未获得足以确认其正式全称、统一 API 和源码版本的一手证据。这不构成“BPF 不存在”的结论。可核验的美团材料包括 Camp 广告平台化、BFF 组装式架构和交易系统领域建模,下面按各自公开名称讨论,不把它们合并为一个叫 BPF 的框架。
TMF2 设计者后来公开了 Lattice,并将其定位为简化的相关实现。Lattice 的文档、注解与示例可供验证机制,但不能据此把 Lattice 的类名反写成内部 TMF2 的类名。Lattice 开源说明、项目仓库。
这几种证据分别支持架构意图、公开产品的 API,以及自建示例的运行行为。它们无法证明内部生产环境的性能、发布隔离程度或故障恢复效果。
从业务动作到扩展点
以教学用的预售下单为例,平台对外提供“创建订单”。调用方不需要先知道价格域有多少策略,库存域部署在哪台机器。平台内部再把请求组织为校验、计价、额度处理、订单持久化等动作。
这些名词需要在各自层级上定义:
| 概念 | 教学场景里的例子 | 主要回答的问题 |
|---|---|---|
| 业务活动、领域服务 | 用户下单、创建订单、计算应付金额 | 系统向调用者提供什么完整动作 |
| 领域能力 | 订单计价、销售资格判断、库存预留 | 哪种业务语义可以稳定复用 |
| 扩展点 | 计价中的单价策略、资格判断中的渠道限制 | 哪一部分允许业务定制 |
| 扩展实现 | 预售价、团购价、预售时间校验 | 某种差异具体怎样计算 |
| 商业产品 | 预售、团购、电子凭证 | 哪些跨步骤规则组成可重复使用的业务方案 |
这是一套用于解释的分层,不要求每层都对应一个 Java 包、接口或微服务。一个小系统可以只保留领域服务和扩展接口;为了对齐名词而建立大量空壳,只会增加跳转成本。
领域能力也未必就是扩展接口。订单计价能力可以包含金额校验、币种一致性、舍入规则和多个可变策略。能力的公共实现维持不变量,扩展点只开放允许变化的部分。若任何插件都能覆盖订单总额、跳过资金约束,平台就没有留下可依赖的交易语义。
下面是独立的设计示意,类名不是任何厂商 API:
1 | |
扩展实现决定单价,能力保留数量校验和溢出检查。即便两个业务选择不同单价策略,仍然遵守同一金额契约。仅仅让多个类实现同一个接口,不能保证这种语义一致性;接口文档、契约测试和所有权同样重要。
图 1:教学示意:单价策略可替换,金额校验与溢出检查保留在计价能力内。
flowchart TB
accTitle: 教学示意:单价扩展与公共金额契约
Input["计价上下文与已选策略"] --> Invoke["调用单价策略"]
subgraph Extension["允许业务变化的部分"]
Retail["现货单价"]
Presale["预售单价"]
end
Invoke -.->|本次选择其一| Retail
Invoke -.->|本次选择其一| Presale
Retail --> Price["返回以分计的单价"]
Presale --> Price
subgraph Common["计价能力的公共实现"]
Check{"单价非负且数量为正?"}
Multiply["Math.multiplyExact<br/>单价乘以数量"]
Overflow{"乘法是否溢出?"}
Total["返回总金额"]
Reject["抛出异常,终止计价"]
Check -->|是| Multiply --> Overflow
Check -->|否| Reject
Overflow -->|否| Total
Overflow -->|是| Reject
end
Price --> Check
Lattice 关于能力的早期说明把能力与其作用对象、扩展点集合联系起来。这能帮助解释“能力是一组可扩展行为”,但早期概念接口不能与后续版本的 Quickstart 任意拼接成代码。Lattice:能力。
业务身份怎样关联商业产品
业务身份是路由规则所使用的稳定分类,不等于用户 ID、租户 ID、订单 ID,也不等于机器地址。教学示例用 retail、presale、group 作为三个身份;实际交易模型可能从市场、渠道和商品属性中识别业务。
TMF 的公开模型区分垂直业务与可叠加的水平产品。业务身份关联可用的产品组合,某个产品本次是否生效还可能取决于业务会话。一个垂直业务采用电子凭证能力,并不意味着每笔订单都选择电子凭证。Lattice:业务概念。
因此至少存在两道选择:
1 | |
业务身份不应成为无限拼接的字符串。把行业、会员等级、促销活动、地域、渠道全拼进一个 code,容易形成组合爆炸。较合理的教学建模是:用业务身份选择规则空间,用产品配置表达可复用方案,用请求属性参与条件判断。哪些维度属于身份,要根据领域中的管理责任和隔离要求决定。
商业产品也不等于面向消费者售卖的 SKU。“预售”可以是平台内部的一套方案,跨计价、库存和履约分别提供实现。某个实现是否由平台团队维护,不改变它的归属:平台维护的通用预售规则仍可以作为可选产品被不同业务安装。
图 2:教学模型中的业务身份、水平产品与跨领域扩展;安装关系不代表本次全部执行。
flowchart TB
accTitle: 业务身份通过水平产品关联跨领域扩展实现
A["业务身份 A"] -->|安装| P["水平产品:预售"]
B["业务身份 B"] -->|安装| P
B -->|安装| V["水平产品:电子凭证"]
P -->|提供| PriceImpl["预售价实现"]
P -->|提供| StockImpl["预售额度实现"]
subgraph Pricing["计价领域能力"]
PricePoint["单价策略扩展点"]
end
subgraph Inventory["库存领域能力"]
StockPoint["额度处理扩展点"]
end
PriceImpl -.->|实现契约| PricePoint
StockImpl -.->|实现契约| StockPoint
业务身份的传递必须有可信来源。Web 入口可根据已认证的应用和商品归属解析身份,并将结果随命令传递;异步消息需要保留重建业务上下文所需的信息。不能让调用方随意传 bizCode=privileged 就启用更宽松的规则。统一业务身份解决分类与关联,授权仍然是独立职责。
图 3:教学示意:业务身份从可信信息解析,经命令和消息传递;授权单独检查。
sequenceDiagram
title 教学示意:可信业务身份的同步传递与异步重建
participant C as 调用方
participant W as Web入口
participant D as 领域服务
participant Q as 消息通道
participant A as 消费服务
C->>W: 认证凭据、商品信息和请求参数
W->>W: 根据已认证应用与商品归属解析身份
Note over C,W: 客户端自报 bizCode 不直接作为可信身份
W->>W: 独立检查本次操作权限
alt 授权未通过
W-->>C: 拒绝请求
else 授权通过
W->>D: 命令携带解析后的业务上下文
D->>D: 按业务身份选择规则空间
opt 后续需要异步处理
D->>Q: 事件与重建上下文所需的信息
Q->>A: 通过受控消息链路投递
A->>A: 校验来源并重建业务上下文
A->>A: 执行异步业务规则
end
D-->>W: 业务结果
W-->>C: 返回结果
end
架构需要同时表达定义、配置和执行
图 4:教学架构中的能力契约、业务配置与请求执行。
flowchart TB
accTitle: 教学架构中的能力契约、业务配置与请求执行
subgraph Define["代码定义与构建"]
Contract["领域服务与扩展点契约"]
Common["平台公共实现"]
Plugin["业务与产品插件"]
Meta["能力目录与实现注册表"]
Contract --> Common
Contract --> Plugin
Common --> Meta
Plugin --> Meta
end
subgraph Manage["管理与发布"]
Biz["业务身份"]
Product["产品安装关系"]
Decision["候选优先级与组合规则"]
Version["校验后的配置版本"]
Biz --> Product --> Decision --> Version
Meta --> Decision
end
subgraph Run["一次请求"]
Context["可信身份与请求属性"]
Plan["固定本次配置快照"]
Select["按调用点选择实现"]
Invoke["执行并记录结果"]
Context --> Plan --> Select --> Invoke
Meta --> Select
Version --> Plan
end
代码定义回答“有哪些能力和实现”,配置回答“谁采用谁”,请求上下文回答“这次适用谁”。这三份信息合起来才足以执行。
管理域可以提前发现未知能力、缺失配置、重复实现和可静态识别的规则冲突。运行域仍然需要计算金额、检查资格、访问库存。所谓静态期决策,不是把所有业务计算都提前算完,而是尽量提前确定可用规则、组合关系和冲突处理方式。
平台团队的责任也因此更具体:维护语义稳定的契约、验证候选配置、给出默认行为、解释实际路由结果。业务团队在这个契约内实现差异。代码仓库分开只是开发隔离;如果所有插件仍共享一个 JVM、线程池和发布进程,资源与故障隔离并没有自动完成。
图 5:教学示意:仓库拆分后,共享 JVM 和线程池仍可能让一个插件的故障影响其他业务。
flowchart TB
accTitle: 教学示意:代码分离与共享运行资源的故障传播
RepoA["业务 A 代码仓库"] --> PluginA["插件 A"]
RepoB["业务 B 代码仓库"] --> PluginB["插件 B"]
subgraph Runtime["同一发布进程与 JVM"]
PluginA
PluginB
Pool["共享线程池"]
Heap["共享堆内存"]
PluginA --> Pool
PluginB --> Pool
PluginA --> Heap
PluginB --> Heap
end
Slow["A 的外部调用持续阻塞"] -->|工作线程长期占用| Pool
Pool -->|可用线程不足时| Delay["B 的请求也可能排队或超时"]
Memory["A 持续分配内存"] -->|堆压力升高| Heap
Heap -->|GC 或进程失败时| Impact["同一 JVM 内业务共同受影响"]
Boundary["隔离设计需另行定义<br/>线程池配额、超时与进程边界"] -.-> Pool
Boundary -.-> Runtime
美团公开实践展示了两种不同的组合方式
Camp:能力目录与数据依赖图
美团外卖广告的 Camp 文章公开了 @LppAbility 与 @LppExtension。前者描述原子能力,后者描述具体扩展;框架在编译阶段采集元数据。它还分析 Action 的数据依赖以构建 DAG,再由场景配置和调度执行组合结果。美团:广告平台化的探索与实践,2022-03-03。
这个方案提示了一种重要区别:能力选择关注用哪个实现,DAG 编排关注执行先后与可并行关系。接口实现可替换,不代表执行图已经确定。
一个广告教学场景可以先读取广告候选,再并行取得预算与定向结果,最后过滤并排序:
图 6:广告教学场景中的数据依赖与可并行节点。
flowchart LR
accTitle: 广告教学场景中的数据依赖与可并行节点
A["候选广告"] --> B["预算信息"]
A --> C["定向结果"]
B --> D["过滤"]
C --> D
D --> E["排序"]
这张图是教学推导,不是 Camp 原图。只有预算查询与定向判断不存在其他共享副作用时,图上的并行才是安全的。读取字段之间没有依赖,不足以证明数据库更新、锁、外部服务限额也没有依赖。
BFF:复用预组装结构,选择场景子图
美团的 BFF 文章使用统一业务身份关联组件组装 DSL 和配置,并讨论从预组装活动图中选择所需子图。它针对的是跨领域的数据查询和展示组装,不能直接搬成交易写入的事务模型。美团:标准化思想及组装式架构在后端 BFF 中的实践,2022-04-28。
例如“带筛选的商品货架”与“不带筛选的商品货架”,可以共用商品查询和卡片组装,只在前者激活筛选数据组件。这是对既有组合空间的选择,通常比允许任意拖动所有节点更容易验证。
图 7:教学示意:两种商品货架复用查询和卡片组件,带筛选场景额外启用筛选数据。
flowchart TB
accTitle: 教学示意:商品货架按场景选择组件子图
Identity["业务身份与场景配置"] --> Select["从预组装活动图选择子图"]
subgraph WithFilter["带筛选的商品货架"]
Q1["商品查询"] --> C1["卡片组装"]
F1["筛选数据组件"] --> V1["页面结果组装"]
C1 --> V1
end
subgraph Plain["不带筛选的商品货架"]
Q2["商品查询"] --> C2["卡片组装"] --> V2["页面结果组装"]
end
Select -->|选择带筛选场景| Q1
Select -->|启用筛选组件| F1
Select -->|选择普通场景| Q2
Q1 -.->|复用同一组件定义| Q2
C1 -.->|复用同一组件定义| C2
广告数据流、展示活动图、交易规则叠加,都可能采用组件和配置,却不应被强行压成同一套扩展模型。场景决定组件颗粒度、输入输出契约以及失败语义。
复用的上限由契约决定
新业务能够只改配置,至少意味着所需能力已经存在、输入输出兼容、执行顺序合法、组合没有破坏不变量。任一条件不满足,仍然需要新增代码或调整领域模型。
图 8:教学决策树——新业务能否通过已有能力的配置组合接入。
flowchart TB
accTitle: 新业务能否通过已有能力的配置组合接入
Need["新业务需求"] --> Exists{"所需能力与实现<br/>已经存在?"}
Exists -->|是| Types{"输入输出类型<br/>及业务含义兼容?"}
Types -->|是| Order{"既有编排能表达<br/>合法执行顺序?"}
Order -->|是| Rules{"状态与补偿语义已定义<br/>且不变量可保持?"}
Rules -->|是| Configure["配置安装关系与规则<br/>完成组合验证后发布"]
Exists -->|否| Develop["补充代码或调整领域模型<br/>重新验证"]
Types -->|否| Develop
Order -->|否| Develop
Rules -->|否| Develop
例如把既有团购计价与既有预售额度组合起来,并不能自动得到“预售团购”。还需要确定成团失败是否退订金、预售额度何时释放、尾款逾期如何处理。这些状态和补偿语义若未定义,几个插件都成功执行也不能证明业务正确。
扩展点过细还有反作用:一个金额计算过程拆成几十个只返回配置值的方法,会让调用者难以理解整体行为。阿里公开的架构反思直接讨论过细粒度扩展点带来的理解和维护成本。阿里云开发者:业务系统架构实践总结,2023-09-28。
判断一个能力是否值得复用,可以比较两个业务的输入含义、结果含义和变化责任。两处代码都返回“10 分钟”不是复用证据;它们是否应在同一业务规则变化时一起改变,才有助于确定边界。
参考资料与适用范围
本篇引用的 TMF、Lattice 与美团材料主要发布于 2018—2023 年,均属于历史设计说明。它们用于解释架构,不用于断言厂商当前的内部部署状态。来源是设计者自述或团队文章,生产效果没有在本次实验中独立复现。
相关实现细节见 第二篇的注解模型与配置表;实际运行结果见 第三篇的 Java 实验。


