DDD 多租户设计到底该不该用 AsyncLocal?90% 项目一开始就选错了
引言:一个被反复问错的问题
在 DDD 项目里,只要一谈到多租户,几乎必然会出现这个问题:
“多租户用 AsyncLocal 行不行?”
甚至很多项目在一开始,就已经默认答案是 “行,而且很方便”。
但如果你认真从 DDD 的视角 来审视这个问题,就会发现:
这不是一个技术技巧选择问题,而是一个架构边界问题。
而遗憾的是,90% 的项目在第一步就选错了方向。
多租户在 DDD 中到底是什么?
先把一个最关键的概念说清楚:
在 DDD 中,多租户不是运行时上下文,而是业务事实(Business Fact)。
也就是说:
- 租户 ≠ 当前线程信息
- 租户 ≠ 技术上下文
- 租户 = 业务规则的一部分
它直接影响:
- 聚合的合法性
- 实体的可访问范围
- 领域不变量
- 授权与边界上下文
一个订单属于哪个租户,本质上和它的金额、状态同一个层级。
AsyncLocal 为什么会“看起来很香”?
我们先承认现实:
AsyncLocal 在工程上确实很诱人。
它解决了三个让人头疼的问题:
- 不想层层传参
- 不想污染方法签名
- 不想每个构造函数都带 TenantId
于是很多项目会这样做:
TenantContext.Current = tenantId;
然后在任意地方直接读取:
var tenantId = TenantContext.Current;
零侵入、零参数、全局可用。
从“写代码爽”的角度看,它几乎是满分。
但这正是问题的开始
1️⃣ AsyncLocal 隐式地破坏了依赖边界
DDD 的一个核心原则是:
依赖必须显式,而不是隐式。
而 AsyncLocal 带来的恰恰是:
- 看不见的依赖
- 无法从构造函数判断对象是否依赖租户
- 无法从方法签名判断业务前置条件
你看到的方法是:
PlaceOrder(order);
但真实的前提是:
“当前 AsyncLocal 中必须已经存在合法的 TenantId”
这在 DDD 中是不可接受的。
2️⃣ 它把业务事实伪装成了技术上下文
AsyncLocal 本质是:
- 线程 / 异步流上下文存储
- 技术设施级能力
但多租户是:
- 业务边界
- 授权约束
- 数据隔离规则
当你用 AsyncLocal 承载租户时,相当于在说:
“这个业务规则是可选的,只是运行时环境的一部分。”
这是一个语义层级的严重错误。
3️⃣ 它让领域模型“看起来纯净,实际上脆弱”
很多人用 AsyncLocal 的真实动机是:
“我不想让 Domain / Application 层看到 TenantId。”
于是你得到一个:
- 表面很干净
- 实际高度依赖运行环境
- 离开 Web 请求就会出问题的领域模型
一旦出现下面场景:
- 后台任务
- 消息消费
- 批处理
- 单元测试
- 并发执行
你就会发现:
领域模型根本不知道自己运行在什么租户下。
显式依赖:DDD 里“难但正确”的选择
在 DDD 中,多租户的正确姿势只有一个关键词:
显式
显式意味着什么?
- TenantId 是构造参数 / 方法参数
- 聚合创建时就绑定租户
- Repository 查询明确以 TenantId 作为条件
- Application 层显式控制租户边界
例如:
public sealed class Order
{
public TenantId TenantId { get; }
public Order(TenantId tenantId, ...)
{
TenantId = tenantId;
}
}
这带来的好处是:
- 业务前置条件一目了然
- 单元测试不依赖运行环境
- 架构边界清晰
- 错误更早暴露
是的,它更啰嗦, 但它是 DDD 语义上正确的设计。
那 AsyncLocal 就一无是处吗?
不是。
AsyncLocal 适合的场景只有一个:
技术上下文的传递,而不是业务建模。
例如:
- TraceId
- CorrelationId
- 审计信息
- 日志上下文
如果你把 AsyncLocal 用在这里,它是加分项。
但一旦你用它承载:
- TenantId
- UserId
- 业务身份
- 授权边界
那它就变成了架构妥协。
为什么 90% 的项目一开始就选错了?
因为他们在用下面的逻辑做决策:
- “这样写最省事”
- “大家都这么用”
- “框架也是这么干的”
- “后面再重构吧”
但 DDD 的现实是:
多租户是地基问题,不是重构级别的问题。
一旦方向错了,后面只会不断打补丁。
结论:这不是技术选择,而是架构立场
如果你真的在做 DDD:
- 多租户必须是显式依赖
- 租户必须是业务事实
- 领域模型必须知道自己属于谁
AsyncLocal 不是“错”, 但它不该出现在多租户建模中。