cover

系列文章:DOM 與架構非同步、CSS 與狀態管理本篇:API 設計與部署

API 設計的演進:SOAP → REST → GraphQL → tRPC

SOAP:企業級的沉重

Web 2.0 之前,系統間通訊靠 SOAP。它一點都不 Simple:

<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUser xmlns="http://example.com/users">
      <UserId>123</UserId>
    </GetUser>
  </soap:Body>
</soap:Envelope>

同樣的操作用 REST:GET /api/users/123。你就知道為什麼 SOAP 被淘汰了。

REST:簡潔的革命

用 HTTP 動詞表達操作、URL 路徑表達資源、JSON 作為資料格式。跟 HTTP 天然契合,不需要額外協議。REST 統治 Web API 十多年,至今仍是最被廣泛使用的風格。

但隨著 SPA 和行動裝置普及,痛點越來越明顯:

GET /api/users/123          → 回傳所有欄位(你只需要名字和頭像)
GET /api/users/123/posts    → 回傳所有文章(你只需要最新 5 篇標題)
GET /api/users/123/followers → 回傳所有追蹤者(你只需要數量)

Over-fetching:每個 endpoint 回傳的資料比你需要的多。Under-fetching:一個 endpoint 不夠,要打三個才能拼出一個頁面。在弱網環境下,多餘的資料傳輸和多次網路往返就是效能殺手。

GraphQL:客戶端決定要什麼(2015)

query {
  user(id: 123) {
    name
    avatar
    posts(last: 5) { title }
    followersCount
  }
}

一個請求、精確的資料、沒有多餘欄位。但也帶來新複雜性:Schema 維護成本、HTTP 快取困難(所有請求都打同一個 endpoint)、N+1 query 問題、學習曲線陡峭。

tRPC:TypeScript 全端的極致

如果前後端都用 TypeScript:

// 後端定義
const appRouter = router({
  getUser: procedure
    .input(z.object({ id: z.number() }))
    .query(({ input }) => db.user.findUnique({ where: { id: input.id } })),
});
 
// 前端直接用——完整型別推導,改後端會立即在前端報錯
const user = trpc.getUser.useQuery({ id: 123 });

完全跳過 Schema 定義,型別直接從後端推導到前端。但只適用於 TypeScript 全端專案——如果你有 iOS / Android 客戶端,tRPC 就不行了。

選擇指南:

  • 大多數團隊 → REST(最務實)
  • 資料需求複雜且變動頻繁 → GraphQL
  • TypeScript 全端小團隊 → tRPC
  • 沒有人應該選 SOAP

部署的演進:FTP → CI/CD → Docker → Serverless

FTP 時代:用勇氣部署

打開 FileZilla,把檔案拖到伺服器。沒有版本控制——改壞了就祈禱自己記得改了哪些。兩個人同時改同一個檔案?最後上傳的那個直接覆蓋前一個人的工作,沒有合併、沒有衝突提示。

回滾就是把「之前的檔案」再傳上去——前提是你還留著。沒留著?那就寫遺書吧。

CI/CD:讓機器來做

Git 解決了版本控制,CI/CD(Jenkins、GitHub Actions)解決了自動化部署:

on:
  push:
    branches: [main]
jobs:
  deploy:
    steps:
      - run: npm test
      - run: npm run build
      - run: deploy-to-production

推 code 後自動測試、自動建置、自動部署。整個流程標準化、可重複、有紀錄。消除了大部分「在我的電腦上可以跑」的問題——但還沒有完全解決,因為環境可能還是不一樣。

Docker:把環境打包帶走(2013)

FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["node", "dist/server.js"]

不只打包程式碼,連執行環境一起打包。在 Mac 上跑、在 CI 上跑、在雲端跑——結果都一模一樣。Docker 還催生了微服務架構,每個服務獨立打包、獨立部署、獨立擴展。

但你仍然要管伺服器——監控健康狀態、管理資源分配、處理安全更新。

Serverless:連伺服器都不管了

exports.handler = async (event) => {
  const user = await db.getUser(event.pathParameters.id);
  return { statusCode: 200, body: JSON.stringify(user) };
};

有請求就自動處理,沒請求不計費。不用管 Nginx、不用擔心擴展。

限制也很明顯:冷啟動延遲、執行時間限制(通常 15 分鐘)、不適合有狀態的長連線、vendor lock-in。

整條演進鏈的方向一直很明確:減少人為操作,增加自動化和標準化。 從手動 FTP 到自動 pipeline,從手動配置環境到容器化,從管伺服器到不碰伺服器——每一步都在把開發者從基礎設施中解放出來。

系列總結:理解演進,才能做出選擇

回顧整個系列,有一個反覆出現的模式:

痛點 → 解決方案 → 新痛點 → 新解決方案

這不是退步,是在不同 trade-off 之間螺旋上升。理解這些「為什麼」,有三個實際好處:

做出更好的技術選型。 你會問:「這個工具解決的問題,是不是我真正面對的問題?」而不是被社群熱度左右。

更快學會新技術。 理解了 Callback → Promise → async/await 的脈絡,學任何語言的非同步處理都會很快。

預測未來方向。 看到一個技術的痛點被越來越多人討論,你可以推測下一代方案會往哪走。

不是每個新工具都適合你的場景。一個簡單的內容網站不需要 React,一個三人團隊的 MVP 不需要 Kubernetes。技術選型不是選最新的,是選最適合你當前問題的。


不要追逐潮流。理解 trade-off,然後做出清醒的選擇。


延伸閱讀