FDE Times 数据在招 FDE 职位 798▼30 近 7 天在招职位数的变化(同口径比较):不计入本周新开始追踪的公司的职位近 7 天新增 67在招公司 139支持远程 25%美国薪资中位数 $202k招聘最多 Databricks 108
ENVI

前线部署工程师(FDE)的专业媒体

技能指南 · 指南

实战:用 Hono 在 Cloudflare Workers 上搭一层轻量 API,封装客户的遗留系统

客户的系统通常改不了。但你仍然可以在它前面加一层干净、带类型、有认证的 API,旧代码一行都不用动。

实战:用 Hono 在 Cloudflare Workers 上搭一层轻量 API,封装客户的遗留系统
图片:Emile Perron / Unsplash: 图片:Emile Perron / Unsplash
分享微博

要点速览

  • Hono 在 Cloudflare Workers 上运行得很顺:用 create-hono 生成项目骨架,再用调用 Wrangler 的 npm 脚本部署。
  • 封装层必须在坏请求到达客户系统之前把它拦下:检查 token、校验载荷、映射错误。
  • token 放进 secret,不要把单次请求的数据存在模块级变量里,上生产环境之前打开可观测性。
Cloudflare Workers 上 Hono 封装层的示意图。智能体或看板向 Worker 发送 { sku, qty },请求依次经过 Bearer 认证、Zod schema 和字段改名步骤,以 ITEM_CODE、QUANTITY 的形式到达遗留系统。没有 token 的请求在 Bearer 认证处得到 401,qty: -3 的载荷在 Zod 处得到 400,二者都碰不到遗留系统。遗留系统返回的错误交给作为兜底层单独处理的 onError,以 JSON 错误的形式返回。
封装层在请求到达遗留系统之前,拦下缺少 token 的请求(401)和格式错误的载荷(400)。来自后端的错误由 app.onError 汇总成统一的 JSON 响应。来源:文中步骤,依据 Hono 和 Cloudflare 文档。

到客户现场的第一周,这样的场景很常见:你的智能体或看板需要的数据,存放在一套用了十年的订单系统里。字段名全是大写,报错返回的是 HTML,没人敢动它的代码。你也不必去动。你要做的,是在它前面搭一层轻量的封装。

这正是前线部署工程师(FDE)招聘启事里写明的工作。Synapse Analytics 招聘 Forward Deployed Engineer II 的启事要求候选人构建并维护面向具体客户的集成组件,包括适配器、连接器、脚本和 API,并把反复出现的模式沉淀为产品改进。

本文用 Hono 和 TypeScript 在 Cloudflare Workers 上搭建这样一层。根据 Cloudflare 的文档,Hono 是一个轻量框架,在 Workers 上运行效果很好。为了便于阅读,下面的代码做了精简,交付给客户之前请对照官方文档核实。

要做什么,需要准备什么

举一个假设的例子:客户有一个遗留订单 API,接收 ITEM_CODE 和 QUANTITY。你的封装层对外提供 POST /api/orders 接口,接收干净的 JSON:{ sku, qty }。它检查调用方的 token,校验载荷,用自己的 token 调用遗留系统,并把下游的每一种失败都转换成统一的 JSON 错误。

你需要 Node.js 和 npm;如果要部署,还需要一个 Cloudflare 账号;以及被封装系统的 URL 和 token。在家练习的话,任何你有权调用的内部 API 都可以。

第 1 步:项目骨架和第一个接口

Hono 提供 create-hono 命令来生成项目骨架。运行下面的命令,出现提示时选择 Cloudflare Workers 模板:

npm create hono@latest orders-wrapper
cd orders-wrapper

入口文件是 src/index.ts,它把 app 作为默认处理器导出。加一个健康检查接口,确认一切正常运行:

import { Hono } from 'hono'

const app = new Hono()
app.get('/health', (c) => c.json({ ok: true }))

export default app

运行 npm run dev,打开 Wrangler 打印出的本地 URL。检查:/health 必须返回 {"ok":true}。打开 package.json,看看 dev 和 deploy 两个脚本是怎样调用 Wrangler 的。整个项目都会用到这两条命令。

第 2 步:客户的 token 放在哪里?

很多人一开始就在这里出错。Cloudflare 的最佳实践指南说得很明确:API 密钥、token 和数据库凭据绝不能出现在 Wrangler 配置文件或源代码里。要把它们放进 secret:

npx wrangler secret put UPSTREAM_TOKEN
npx wrangler secret put CLIENT_TOKEN

用 npm run dev 在本地运行时,Wrangler 会从项目根目录的 .dev.vars 文件读取 secret。这里用测试 token,不要用客户的真实 token,并把这个文件加进 .gitignore:

UPSTREAM_TOKEN=dev-upstream-token
CLIENT_TOKEN=dev-client-token

Cloudflare 还建议不要手写 Env 接口,而是用 wrangler types 生成。每次新增绑定后都运行一次:

npx wrangler types

然后把第 1 步里的 const app = new Hono() 改成下面这一行。这是替换,不是再声明一个 app:

const app = new Hono<{ Bindings: Env }>()

**检查:**在编辑器里输入 c.env.,应当能提示出正确的变量名。遗留系统的 URL(UPSTREAM_URL)不是机密,可以作为普通配置变量。

第 3 步:在坏请求到达客户系统之前拦下它

在做任何转换之前,封装层得先学会说“不”。Hono 的 Bearer Auth 中间件不要求 token 必须是 JWT,你可以用 verifyToken 函数自己检查 token:

import { bearerAuth } from 'hono/bearer-auth'

app.use('/api/*', bearerAuth({
  verifyToken: async (token, c) => token === c.env.CLIENT_TOKEN,
}))

接下来是载荷校验。Zod 是为 TypeScript 设计的校验库,Hono 的 validator 中间件可以校验 json、query、header、param、cookie 和 form。安装依赖包并声明 schema:

npm i zod @hono/zod-validator
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

const OrderInput = z.object({
  sku: z.string().min(1),
  qty: z.number().int().positive(),
})

**检查:**在第 4 步把 schema 挂到路由上之后,不带 Authorization 请求头的请求必须得到 401;发送 qty: -3 必须被 400 拒绝,下游代码一行都还没来得及执行。

第 4 步:翻译成遗留系统的语言,集中处理错误

到这一步才开始转换。处理器拿到已校验的数据,把字段改名以匹配遗留系统,再用遗留系统自己的 token 调用它:

import { HTTPException } from 'hono/http-exception'

const route = app.post('/api/orders', zValidator('json', OrderInput), async (c) => {
  const { sku, qty } = c.req.valid('json')
  const res = await fetch(`${c.env.UPSTREAM_URL}/createOrder`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${c.env.UPSTREAM_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ ITEM_CODE: sku, QUANTITY: qty }),
  })
  if (!res.ok) throw new HTTPException(502, { message: '订单系统不可用' })
  return c.json(await res.json(), 201)
})

遗留系统返回 HTML 错误页时,绝不能让调用方看到。Hono 允许你抛出 HTTPException,并用 app.onError 在一处统一处理错误:

app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json({ error: err.message }, err.status)
  }
  console.error(err)
  return c.json({ error: '内部错误' }, 500)
})

**检查:**临时把 UPSTREAM_URL 指向一个错误地址。调用方必须收到带 error 字段的 JSON,绝不能是堆栈信息或 HTML。

第 5 步:共享类型并部署

如果调用这个 API 的代码(比如智能体或前端)也由你来写,Hono 的 RPC 功能可以让服务端和客户端共享 API 规范。只需要导出 app 的类型:

export type AppType = typeof route

客户端导入 AppType,马上就能得到一个带类型的客户端。在服务端改了某个字段名,客户端会在编译时报错,而不是在客户现场出错。Cloudflare 还提到,如果之后需要用户界面,可以通过 Workers Assets 把 Hono API 和 SPA 组合在一起。

最后运行 npm run deploy。Worker 可以运行在 *.workers.dev 子域名或自定义域名上,可以从笔记本部署,也可以从任何 CI/CD 系统部署。在客户现场,第一周就把部署命令放进 CI,这样谁都不必依赖你的电脑。

让封装层在生产环境出问题的几个错误

最难发现的一个:Workers 会在多个请求之间复用 isolate。如果你把 token 或某次请求的数据存在模块级变量里,下一个请求可能会读到上一个请求的数据。凡是属于请求的东西,都必须放在处理器内部,并且从 c 获取。

其次是忽视套餐限制。Free 套餐每个请求允许 10 ms CPU 时间,每次调用最多 50 个子请求。设想一个“订单详情”接口,每个订单行都调用一次遗留系统:一个有 60 行的订单会直接超限。要么把调用合并成批量请求,要么在向客户承诺之前先核对套餐。

最后是部署之后什么都看不见。Cloudflare 把没有可观测性的生产 Worker 形容为一个黑盒。在 Wrangler 配置文件里打开它(下面是 wrangler.jsonc 的精简示例;如果你的模板用的是 wrangler.toml,请声明等价配置),然后重新部署:

{
  "observability": { "enabled": true }
}

一定要在第一次有人问你“凌晨 2 点那笔订单为什么失败”之前做好这件事。

在客户现场怎样起步,简历上怎样写

在客户现场,第一项任务不是写代码,而是列清三件事:真正需要哪些接口,谁来调用,遗留系统怎样报错。在此基础上,做出一个读接口和一个写接口,当周就有东西可以演示。

简历上不要笼统地写“构建了 API”。要写清楚封装的是哪套系统,用了哪种认证,拦截了哪几类坏载荷,下游错误是怎样映射的。

这正是 FDE 招聘启事里“适配器、连接器、API”那一条所用的语言。当你封装到第三套系统,又一次看到同样的模式时,就该去告诉产品团队了。

这篇文章对你有帮助吗?

10 个信源
路线图上的下一篇 · 阶段 5: 部署上线FDE 的反向 ETL 实践:把 AI 评分送进销售团队真正在用的 Salesforce评分模型再准,如果结果只躺在数据仓库的某张表里,对销售团队来说就等于不存在。