Effect 的 LayerMap 怎么做多租户 Layer 缓存?
我连续三次顺序 provide 同一个 key 的 LayerMap,得到的是三次构建,缓存完全没起作用。
多租户的 Layer 缓存用 LayerMap:get(key) 返回一个 Layer,业务代码照常 Effect.provide;而且必须设置 idleTimeToLive,否则缓存只在借用期间存在。 这是 4.0.5 里的行为,直觉上很容易漏掉。
LayerMap 的底层是 RcMap,也就是「按 key 共享、引用计数释放」的资源;它只是把 key 对应的东西从资源换成了整套 Layer。如果想先弄清五个资源 API 各管什么,看 Effect 的 LayerMap、RcMap、Pool 该怎么选?。下面是写法和三个容易出错的行为。
多租户的 LayerMap 怎么写?
LayerMap 的 rcMap 字段直接暴露了底层的 RcMap,它让业务函数只写 yield* TenantDb,租户在边界处选择:
import { Context, Effect, Layer, LayerMap } from "effect"
class TenantDb extends Context.Service<TenantDb, {
readonly tenant: string
readonly query: (sql: string) => Effect.Effect<string>
}>()("app/TenantDb") {
static readonly layer = (tenant: string) =>
Layer.effect(
TenantDb,
Effect.acquireRelease(
Effect.sync(() =>
TenantDb.of({ tenant, query: (sql) => Effect.succeed(`[${tenant}] ${sql}`) })
),
(db) => Effect.log(`release ${db.tenant}`)
)
)
}
class Tenants extends LayerMap.Service<Tenants>()("app/Tenants", {
lookup: (tenant: string) => TenantDb.layer(tenant),
idleTimeToLive: "1 minute"
}) {}
const listUsers = TenantDb.use((db) => db.query("select * from users"))
const program = Effect.gen(function*() {
const a = yield* listUsers.pipe(Effect.provide(Tenants.get("acme")))
const b = yield* listUsers.pipe(Effect.provide(Tenants.get("globex")))
yield* Tenants.invalidate("acme") // 下次访问重建
console.log(a, b)
}).pipe(Effect.provide(Tenants.layer))
Effect.runPromise(program)listUsers 完全不知道租户的存在;用哪个租户,由 Effect.provide(Tenants.get("acme")) 在调用处决定。如果你对 Layer 和服务的关系还不熟,先看 Effect 的 Layer 是什么,依赖图怎么组装?。
同一个 key 会重复构建吗?
不会。同一个 key 连续两次 Effect.provide(Tenants.get("acme")),只构建一次;四个并发的同 key 调用也只构建一次。
为什么不设 idleTimeToLive 就每次都重建?
不写 idleTimeToLive 时,缓存只在借用期间存在。 借用结束,没人持有这个 key 了,缓存项就没了,下一次顺序调用只能重新构建。我连续三次顺序 provide 同一个 key,得到三次构建。
想真正跨请求复用,必须设置 idleTimeToLive,比如上面例子里的 "1 minute":最后一个持有者走后,缓存项再保留这么久。
invalidate 之后,正在用的租户会怎样?
invalidate 会让当前缓存项被移除,下一次访问重新构建;已经借出的 context 在其 scope 关闭前仍可用。所以刷新租户配置时不用担心打断正在处理的请求。
key 空间是开放的还是固定的?
lookup 是函数,key 空间可以是开放的。key 空间固定时用 layers 选项(或 LayerMap.fromRecord),类型上 key 会收窄成字面量联合,写错租户名在编译期就能发现。
另外 contextEffectOption 只读已缓存的项,不会触发构建,适合探测。
还有一件事要记住:LayerMap 在 4.0.5 里是 unstable,小版本里 API 可能变。我建议只在应用层用一处封装,别让它散落在业务代码里。标签含义见 Effect v4 的稳定性标注:@stability 标签取代 unstable 目录。
版本是 effect@4.0.5。同 key 只构建一次、invalidate后重建、不设idleTimeToLive时顺序三次构建三次,这些行为在这个版本上跑过;LayerMap的稳定性标签来自扫描该版本的dist/*.d.ts,它是 unstable,后续版本可能变。