如何让 Encore.ts 各服务的初始化代码只在所属容器中执行?
数据小陈
调用初始化函数前,可以检查 appMeta().deploy.hostedServices 是否包含相应服务。该字段按服务名称记录当前部署单元托管的服务,可用于限制初始化代码的执行范围。使用前,先确认项目所用的 SDK 提供该字段,具体定义见 Encore SDK 源码。
为什么所有初始化函数都会执行?
示例中的 initServiceA() 和 initServiceB() 是在模块顶层直接调用的普通函数。只要对应模块被加载并求值,调用就会执行;函数名称和所在目录不会自动限制它们的服务范围。
题目描述的部署中,多个服务模块都会被加载,因此每个容器都可能输出两条初始化日志。需要为这些带副作用的调用加上明确的执行条件。
根据托管服务信息限制初始化
分别修改两个文件:
// serviceA/index.ts
import { appMeta } from "encore.dev";
function initServiceA() {
console.log("Service A initialized");
}
if (appMeta().deploy.hostedServices["serviceA"]) {
initServiceA();
}
// serviceB/index.ts
import { appMeta } from "encore.dev";
function initServiceB() {
console.log("Service B initialized");
}
if (appMeta().deploy.hostedServices["serviceB"]) {
initServiceB();
}
这里的 serviceA、serviceB 必须与 Encore 中声明的服务名称一致,不能仅凭目录名推断。
如果一个容器只托管 serviceA,判断条件就只会允许 initServiceA() 执行。如果同一个运行单元托管两个服务,两个初始化函数都执行也符合部署配置。
如何确认判断条件生效?
可以临时记录运行时返回的服务列表:
import { appMeta } from "encore.dev";
console.log(
"Hosted services:",
Object.keys(appMeta().deploy.hostedServices)
);
部署后,分别检查各容器的服务列表和初始化日志。如果 serviceA 容器仍列出了 serviceB,需要进一步检查服务分组和部署配置;如果列表只有 serviceA,却仍出现 B 的初始化日志,就应查找是否还有其他未加条件的初始化入口。
不要用 currentRequest() 判断启动阶段所属的服务。初始化时通常没有请求上下文,此时它会返回 undefined,这一行为在 Encore Metadata 文档中有说明。
初始化逻辑还要注意什么?
- 将建立连接、启动后台任务等有副作用的操作放在条件内部。如果导入的模块已经在顶层执行这些操作,只包住后面的日志或函数调用无法阻止它们。
- 保持 Encore 要求的服务、API 和基础设施声明结构,不要把这些声明一起移入条件分支。
- 这一判断只限制执行位置。重启、扩容或多个运行进程仍可能分别执行初始化,涉及共享数据写入时需要考虑幂等性。
- 如果实际初始化函数是异步的,还需要处理失败,并确保依赖初始化结果的请求等待其完成。示例沿用原问题中的同步函数,不提供异步启动就绪保证。
- 如果项目类型定义中没有
deploy.hostedServices,应先核对 SDK 和运行时的兼容性,不要通过类型断言假定该字段存在。
备注:内容仅供参考。
