FDE PulseViệc làm FDE đang mở 618Mới trong 7 ngày 33Công ty đang tuyển 94Nhận làm từ xa 24%Lương trung vị (Mỹ) $209kTuyển nhiều nhất Databricks 121
EN

Tờ báo của nghề Forward Deployed Engineer

Bách khoa

Thực hành: dựng lớp API mỏng bằng Hono trên Cloudflare Workers để bọc hệ thống cũ của khách

Hệ thống của khách thường không sửa được, nhưng bạn vẫn có thể đặt trước nó một lớp API sạch, có kiểu và có xác thực mà không phải đụng vào một dòng code cũ nào.

Kỹ sư ngồi trước laptop đang viết code trong văn phòng, hoặc hàng máy chủ trong phòng server
Ảnh: Emile Perron / Unsplash
Chia sẻLinkedInFacebookX

Tóm tắt nhanh

  • Hono chạy gọn trên Cloudflare Workers: tạo project bằng create-hono, deploy bằng npm script gọi Wrangler.
  • Lớp bọc phải chặn request sai trước khi chúng chạm tới hệ thống của khách: kiểm tra token, kiểm tra payload, ánh xạ lỗi.
  • Token để trong secret, không lưu dữ liệu của từng request vào biến cấp module, bật observability trước khi lên production.
Sơ đồ lớp bọc Hono trên Cloudflare Workers. Agent hoặc dashboard gửi { sku, qty } vào Worker. Request đi qua Bearer Auth, Zod schema rồi bước đổi tên trường, sau đó tới hệ thống cũ dưới dạng ITEM_CODE, QUANTITY. Request không có token bị trả 401 ở Bearer Auth, payload qty: -3 bị trả 400 ở Zod, nên cả hai không chạm tới hệ thống cũ. Lỗi từ hệ thống cũ đi vào onError, được tách riêng thành lớp bắt lỗi, và trả ra lỗi JSON.
Lớp bọc chặn request thiếu token (401) và payload sai (400) trước khi chúng chạm tới hệ thống cũ. Lỗi từ phía sau được app.onError gom lại thành JSON thống nhất. Nguồn: các bước trong bài, dựa trên tài liệu của Hono và Cloudflare.

Tuần đầu ở chỗ khách, bạn hay gặp cảnh này: dữ liệu mà agent hoặc dashboard của bạn cần nằm trong một hệ thống đơn hàng mười năm tuổi. Tên trường viết hoa toàn bộ, lỗi trả về dạng HTML, và không ai dám sửa code của nó. Bạn cũng không cần sửa. Việc của bạn là dựng một lớp mỏng đứng trước nó.

Đây đúng là loại việc mà các tin tuyển FDE ghi thẳng vào mô tả công việc. Tin tuyển Forward Deployed Engineer II của Synapse Analytics yêu cầu ứng viên dựng và bảo trì các thành phần tích hợp riêng cho từng khách, gồm adapter, connector, script và API, rồi biến những mẫu lặp lại thành cải tiến cho sản phẩm.

Bài này dựng một lớp như vậy bằng Hono và TypeScript trên Cloudflare Workers. Theo tài liệu của Cloudflare, Hono là framework nhẹ và chạy rất hợp với Workers. Phần code dưới đây được rút gọn cho dễ theo dõi, nên đối chiếu với tài liệu chính thức trước khi đưa cho khách dùng.

Bạn sẽ dựng gì, và cần chuẩn bị gì?

Ví dụ giả định: khách có một API đơn hàng cũ nhận ITEM_CODE và QUANTITY. Lớp bọc của bạn mở ra endpoint POST /api/orders nhận JSON gọn gàng { sku, qty }, kiểm tra token của bên gọi, kiểm tra payload, gọi hệ thống cũ bằng token riêng, và đổi mọi sự cố phía sau thành lỗi JSON thống nhất.

Bạn cần Node.js và npm, một tài khoản Cloudflare nếu muốn deploy, cùng URL và token của hệ thống cần bọc. Khi luyện tập ở nhà, bất kỳ API nội bộ nào bạn có quyền gọi đều dùng được.

Bước 1: khung project và endpoint đầu tiên

Hono có lệnh create-hono để tạo project. Chạy lệnh dưới đây, rồi chọn template Cloudflare Workers khi được hỏi:

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

Entry point là src/index.ts, export app làm handler mặc định. Thêm một endpoint health check để chắc chắn mọi thứ đã chạy:

import { Hono } from 'hono'

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

export default app

Chạy npm run dev rồi mở URL local mà Wrangler in ra. Kiểm tra: /health phải trả về {"ok":true}. Mở package.json để xem các script dev và deploy gọi Wrangler như thế nào. Hai lệnh này sẽ đi cùng bạn suốt dự án.

Bước 2: token của khách nằm ở đâu?

Đây là chỗ nhiều người làm sai ngay từ đầu. Hướng dẫn best practices của Cloudflare nói rõ: API key, token và thông tin đăng nhập database không bao giờ được xuất hiện trong file cấu hình Wrangler hay trong source code. Hãy đưa chúng vào secret:

npx wrangler secret put UPSTREAM_TOKEN
npx wrangler secret put CLIENT_TOKEN

Khi chạy local bằng npm run dev, Wrangler đọc secret từ file .dev.vars ở thư mục gốc của project. Dùng token thử, không dùng token thật của khách, và thêm file này vào .gitignore:

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

Cloudflare cũng khuyên không tự viết interface Env mà sinh nó bằng wrangler types. Chạy lệnh này mỗi khi bạn thêm binding mới:

npx wrangler types

Sau đó sửa dòng const app = new Hono() ở bước 1 thành dòng dưới đây. Đây là thay thế, không phải khai báo thêm một app thứ hai:

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

Kiểm tra: gõ c.env. trong editor phải gợi ý đúng tên các biến. URL của hệ thống cũ (UPSTREAM_URL) không phải bí mật nên có thể để làm biến cấu hình thông thường.

Bước 3: chặn request xấu trước khi nó chạm tới khách

Trước khi dịch bất cứ thứ gì, lớp bọc phải biết nói “không”. Middleware Bearer Auth của Hono không bắt token phải là JWT, và bạn có thể tự kiểm tra token bằng một hàm verifyToken:

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

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

Tiếp theo là kiểm tra payload. Zod là thư viện validation thiết kế riêng cho TypeScript, và validator middleware của Hono kiểm tra được json, query, header, param, cookie và form. Cài gói rồi khai báo 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(),
})

Kiểm tra: sau khi gắn schema vào route ở bước 4, gửi request không có header Authorization thì phải nhận 401. Gửi qty: -3 thì phải bị từ chối với lỗi 400 trước khi bất kỳ dòng nào phía sau kịp chạy.

Bước 4: dịch sang ngôn ngữ của hệ thống cũ và gom lỗi

Bây giờ mới đến phần dịch. Handler nhận dữ liệu đã được kiểm tra, đổi tên trường cho khớp với hệ thống cũ và gọi sang đó bằng token riêng:

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: 'Order system unavailable' })
  return c.json(await res.json(), 201)
})

Khi hệ thống cũ trả về trang lỗi HTML, bên gọi không nên phải nhìn thấy nó. Hono cho phép ném HTTPException và xử lý tập trung bằng 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: 'Internal error' }, 500)
})

Kiểm tra: trỏ tạm UPSTREAM_URL sang một địa chỉ sai. Bên gọi phải nhận JSON có error, không bao giờ nhận stack trace hay HTML.

Bước 5: chia sẻ kiểu và deploy

Nếu chính bạn viết luôn phần code gọi API, chẳng hạn agent hay frontend, tính năng RPC của Hono giúp server và client dùng chung đặc tả API. Bạn chỉ cần export kiểu của app:

export type AppType = typeof route

Bên client import AppType và có ngay một client đã gắn kiểu. Đổi tên trường ở server là client báo lỗi lúc biên dịch, thay vì báo lỗi ở chỗ khách. Cloudflare cũng ghi chú rằng có thể ghép API Hono với một SPA qua Workers Assets nếu sau này bạn cần thêm giao diện.

Cuối cùng, chạy npm run deploy. Worker có thể chạy trên subdomain *.workers.dev hoặc trên một custom domain, và deploy được từ laptop lẫn từ bất kỳ hệ thống CI/CD nào. Ở chỗ khách, nên đưa lệnh deploy vào CI ngay từ tuần đầu để không ai phụ thuộc vào máy của bạn.

Những lỗi khiến lớp bọc gãy ở production

Lỗi khó thấy nhất: Workers tái sử dụng isolate giữa các request. Nếu bạn lưu token hay dữ liệu của một request vào biến cấp module, request sau có thể đọc được dữ liệu của request trước. Mọi thứ thuộc về một request phải nằm trong handler, lấy từ c.

Lỗi tiếp theo là bỏ qua giới hạn của gói dịch vụ. Gói Free cho 10 ms CPU mỗi request và 50 subrequest mỗi lần gọi. Thử hình dung một endpoint “lấy chi tiết đơn” phải gọi hệ thống cũ một lần cho mỗi dòng hàng: đơn có 60 dòng là vượt ngưỡng ngay. Hãy gom lời gọi lại hoặc xem lại gói dịch vụ trước khi hứa với khách.

Cuối cùng là deploy mà không có cách nào nhìn vào bên trong. Cloudflare gọi một Worker production không có observability là một hộp đen. Bật nó trong file cấu hình Wrangler (ví dụ rút gọn dưới đây cho wrangler.jsonc; nếu template của bạn dùng wrangler.toml thì khai báo tương đương), rồi deploy lại:

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

Làm việc này trước khi lần đầu tiên có người hỏi bạn vì sao đơn hàng lúc 2 giờ sáng bị lỗi.

Bắt đầu ở chỗ khách và ghi vào CV ra sao?

Ở chỗ khách, việc đầu tiên không phải là gõ code mà là liệt kê ba thứ: endpoint nào thật sự cần, ai sẽ gọi, và hệ thống cũ báo lỗi ra sao. Sau đó, từ một endpoint đọc và một endpoint ghi, bạn đã có thứ để demo ngay trong tuần.

Khi viết CV, đừng ghi chung chung là “xây dựng API”. Hãy ghi bạn đã bọc hệ thống gì, dùng cách xác thực nào, chặn loại payload sai nào, và ánh xạ lỗi phía sau ra sao.

Đó chính là ngôn ngữ của mục “adapter, connector, API” trong tin tuyển FDE. Còn khi bạn bọc hệ thống thứ ba mà vẫn thấy lặp lại cùng một mẫu, đó là lúc kể cho đội sản phẩm nghe.

Bài này có hữu ích không?

Dùng cùng trợ lý AIHỏi Claude ↗Hỏi ChatGPT ↗
10 nguồn
Đọc tiếp trên lộ trình · Chặng 5: Triển khaiIAM trên GCP cho FDE: service account, role và cách lần ra lỗi 403 trong dự án của kháchPhần lớn lỗi 403 trong dự án Google Cloud của khách không phải bug: nếu biết cách đọc, chúng thường chỉ ra danh tính nào đang thiếu quyền gì, và cả những lúc vấn đề nằm ngoài IAM.