Federation 运行时
Federation 实例、runtime、serve 和统一入口在 @downcity/city 里的角色。
Federation 是 Downcity 的服务端长期运行容器(文档里也叫 Federation)。它不是一个零散函数集合,而是把 Drizzle db、service、鉴权和统一 /v1/* 路由收在同一个服务端入口里。产品侧访问它用的是 City 客户端,区别见 Federation 与 City。
这个概念在说什么
可以先把 Federation 理解成“多个产品共用的 AI infrastructure runtime 进程”。
它长期持有这些能力:
- Drizzle 数据库连接
- service 注册表
- 服务挂载点
- HTTP 路由入口
- token、city、env 等基础设施
所以你通常不是“调用一下 Federation”,而是“启动一套 Federation runtime,然后让多个产品 client 长期连接它”。
什么时候你需要关心这一页
- 你准备第一次真正把 Federation 跑起来
- 你想理解为什么
Federation只需要一个 Drizzle db - 你已经会写 service,但还不清楚
fetch()、serve()各自用在什么地方 - 你要把 Federation 接到现有 Node 服务、Cloudflare Worker 或别的 HTTP 入口上
什么时候这页不是重点
- 你只想知道产品前端怎么用
City客户端调用:优先读 Client SDK - 你只想知道账号登录怎么跑通:优先读 @downcity/services
- 你只想看某个 custom service 怎么写:优先读 Service 与 Action
最小可运行示例
下面这个例子说明 Federation 运行时最小长什么样:
import { AIService, Federation } from "@downcity/city";
import { serve } from "@hono/node-server";
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
const sqlite = new Database("./.base/downcity.sqlite");
sqlite.pragma("journal_mode = WAL");
const db = Object.assign(drizzle(sqlite), {
$client: { exec: (sql: string) => sqlite.exec(sql) },
});
const base = new Federation({ db });
const ai = new AIService();
ai.use({
id: "local-echo",
name: "Local Echo",
actions: {
text: async (ctx) => ({
id: crypto.randomUUID(),
role: "assistant",
parts: [
{
type: "text",
text: String(ctx.input.prompt ?? ""),
state: "done",
},
],
}),
},
});
base.use(ai);
await base.health();
serve({
fetch: (request) => base.fetch(request),
port: 43127,
hostname: "127.0.0.1",
});这个例子里真正重要的是三件事:
db决定 Federation 使用哪一个 Drizzle 数据库base.use(...)决定 Federation 暴露哪些能力base.fetch()决定你怎么把它接进 HTTP 层
场景一:Federation 自己就是主服务
如果你想让 Federation 自己作为主 HTTP 入口,最直接的理解方式就是上面的 serve():
import { serve } from "@hono/node-server";
await base.health();
serve({ fetch: (request) => base.fetch(request), port: 3001, hostname: "127.0.0.1" });这种方式适合:
- 先快速跑起一个独立 Federation
- 单独部署一个可复用的 AI backend
- 小团队先把 Federation 和业务后端拆成两个清晰边界
场景二:接进你自己的服务框架
如果你已经有现成的 HTTP 框架,更常见的是按请求转给 Federation:
export default {
async fetch(request: Request) {
return base.fetch(request);
},
};这种方式适合:
- Cloudflare Workers
- 你已经有现成网关或 API server
- 想把 Federation 当成内部运行时,而不是单独开一个端口
场景三:本机 HTTP
如果 Federation 只需要被同一台机器上的可信进程访问,可以直接把标准 fetch handler 挂到你的本机 HTTP server:
import { Federation } from "@downcity/city";
import { serve } from "@hono/node-server";
const base = new Federation({ db });
serve({
fetch: (request) => base.fetch(request),
hostname: "127.0.0.1",
port: 15315,
});
const federation_url = "http://127.0.0.1:15315";然后 City 可以直接连接这个 HTTP 地址:
import { City } from "@downcity/city";
const admin = new City({
role: "admin",
federation_url,
city_id: "city_demo",
admin_secret_key,
});
await admin.listServices();本机 HTTP 和远程 HTTP 使用同一套产品侧模型:Admin City 使用 admin_secret_key,公开 action 可以作为 guest 调用,需要用户身份的 action 仍然必须携带正常的 user_token。
如果 AccountsService 配置了 local_login: true,/v1/accounts/providers 会只返回本地登录方式。调用 accounts.login/start 并传入 provider: "local" 与 city_id 后会得到 login_id,再用 accounts.login/result 读取正常 user_token,之后用 city_id + user_token 创建或更新 User City。
为什么只传 db
@downcity/city 负责的是跨运行时都应该稳定的逻辑:
- service 生命周期
- action 路由
- token 鉴权
- city / env / store 基础设施
而具体宿主环境只负责把数据库对象创建好:
- Node.js 可以用
drizzle-orm/better-sqlite3或 Drizzle pg - Cloudflare Workers 可以用
drizzle-orm/d1 - Federation 启动时会把需要的 env 写入内置
env表
这就是为什么同一套 runtime 模型可以复用到不同宿主环境,而不是每换一个平台就重写一套后端。
默认 Storage
如果你的 Federation 需要把运行期产生的外部文件转存到自有存储,可以注册默认 storage:
import { Federation, R2Storage } from "@downcity/city";
const base = new Federation({ db });
base.storage(R2Storage({
bucket: env.DOWNCITY_STORAGE,
public_url_prefix: env.DOWNCITY_STORAGE_PUBLIC_URL_PREFIX,
}));注册后,service 可以通过 ctx.storage 访问这套能力。内置 AI 图片任务会在 image_fetch 成功后自动把远程 file part URL 转存到默认 storage;如果转存失败,会保留上游原始 URL,不会让图片任务失败。
常用 API / 入口
这一页最相关的 Federation API 是:
new Federation({ db }):创建 Federation 实例base.use(...):挂 service、AIService 和官方服务base.storage(...):注册默认文件存储后端base.fetch(request):处理单次请求base.health():触发初始化并返回健康信息base.table(name):拿某张表的受控数据接口
如果你是从“怎么写能力”进入,下一页更关键的是 Service 与 Action。
常见误解
Federation 是 runtime,不是整个 SDK
Federation 是服务端长期运行容器。产品侧真正调用它用的是 City 客户端,见 Client SDK。
Federation 不是纯 HTTP 代理
它背后还持有:
- 内置表
- token / city / env 基础设施
- service 数据层
- hook 生命周期
Federation 不等于某一个 AI provider
provider、model、service、custom service 都只是 Federation 上挂载的能力,不是 Federation 本身。
继续阅读
- 想知道 Federation 里到底挂的是什么,读 Service 与 Action
- 想知道服务和扩展点怎么进去,读 Hook 与服务挂载
- 想知道数据层怎么落地,读 Store 与 Table
- 想看更底层 API,再读 Federation API