CGI 時代:每個請求都是一個新 process
最早的 web server 處理動態內容的方式是 CGI(Common Gateway Interface,1993):
瀏覽器發請求 → Apache → fork 一個新 process 跑腳本 → 輸出 HTML → process 結束
每個請求都 fork 一個新 process。請求量一大,server 就被 fork 的成本拖垮。Perl 和 C 是 CGI 時代的主角,因為它們快。
撞牆點:請求量超過幾十 concurrent 就開始扛不住。process 的記憶體 overhead 太高,fork 的成本太大。
Apache Module 時代:把語言嵌進 server
解法是把語言直接嵌進 web server,不用每次 fork:
- mod_php(1994):PHP 直接跑在 Apache 的 process 裡,不需要 fork
- mod_perl(1996):Perl 也嵌進去
PHP 在這個時代爆炸性成長,因為它讓「在 HTML 裡面直接嵌程式碼」變得容易:
<html>
<body>
<?php echo "Hello, " . $name; ?>
</body>
</html>撞牆點一:效能好了,但程式碼品質爛了。 PHP 讓每個人都可以寫 web,但也讓每個人以不同的方式寫。沒有 MVC、沒有 ORM、資料庫查詢和 HTML 混在一起。專案超過幾千行之後,沒有人知道整個系統怎麼跑的。
撞牆點二:session 管理、路由、安全性每個人都自己實作。 結果是大量的重複和安全漏洞。
Rails 時代:Convention over Configuration
Rails(2004)解的不是效能問題,解的是可維護性問題。
David Heinemeier Hansson 從 Ruby 的動態語言特性出發,做了幾件在當時很激進的事:
- MVC 強制分層:Model / View / Controller 有各自的資料夾,你不能把業務邏輯寫進 View
- Convention over Configuration:你的 User model 自動對應
userstable,不需要任何設定 - ActiveRecord:ORM 直接讓你用物件操作資料庫,SQL 不用手寫
- Generator:
rails generate scaffold User name:string email:string,CRUD 全部生出來
Rails 的影響遠超過 Ruby 生態。Django(Python,2005)、CakePHP(PHP,2005)、Spring MVC(Java)都在同一時期借鑒了這個思路。
撞牆點一:Rails 的 thread 模型。Ruby MRI 有 GIL(Global Interpreter Lock),無法真正並行。Rails 的 Puma / Unicorn 是 multi-process 或 multi-thread,但 context switch 成本和記憶體用量在高並發時成了瓶頸。
撞牆點二:Convention 也是負擔。Rails magic 讓初學者很快上手,但出了問題要 debug 就要讀大量的 DSL 和 meta-programming。而且專案越大,「你以為你改了 A,但其實 B 也動了」這種問題越多。
撞牆點三:JSON API 時代的 overhead。Rails 本來是為了 server-side rendering 設計的。JSON API 不需要 template engine,Rails 帶來的 view layer 整個是多餘的 overhead。
Node.js 時代:Event Loop 解高並發
2009 年,Ryan Dahl 做 Node.js 的出發點是:thread-per-request 模型在高並發下記憶體爆炸。
一個 Apache + PHP 的 server,每個連線會占用一個 thread(或 process),10,000 個並發連線就是 10,000 個 thread——記憶體和 context switch 成本讓 server 撐不住。
Node.js 的 event loop 模型:
單一 thread + non-blocking I/O
→ 一個 process 可以同時「等待」數萬個 I/O 操作
→ 不是真正的並行,但 I/O 等待期間可以處理其他請求
Express(2010)是 Node.js 生態的第一個主流 framework,極度 minimal:只給你路由和 middleware,其他都自己來。這個設計哲學讓它在 microservice 時代大量被用在「只需要做一件事的 service」。
撞牆點一:Callback hell。早期 Node.js 大量用 callback 處理 async,巢狀 callback 讓程式碼難以閱讀和維護。Promise(2015 ES6)和 async/await(2017 ES2017)緩解了這個問題,但語言本身的 async 模型仍有學習曲線。
撞牆點二:Express 什麼都不給,大專案要自己搭所有東西。每個 Express 專案的結構都長得不一樣,onboarding 成本高。NestJS(2017)用 Angular 的架構理念解這個問題——強制 Module / Injectable / Controller,讓 Node.js 大型後端也有統一結構。
撞牆點三:動態型別的大型系統維護困難。TypeScript 的廣泛採用(2018 後)是為了解決這個問題。現在幾乎所有新的 Node.js 後端都用 TypeScript。
async framework 回潮:ASGI、Ktor、Spring WebFlux
Python 的 Django 是同步框架,底層是 WSGI。2018 年之後,async Python 框架開始崛起:
- FastAPI(2018):基於 Starlette(ASGI),native async/await,Pydantic 自動 validation,OpenAPI 文件自動生成
- Starlette(2018):FastAPI 的底層,輕量 ASGI framework
FastAPI 解的是幾個 Django 的痛點:
- Django ORM 是同步的,在高並發 I/O 場景會因為 thread 阻塞拖慢整體吞吐
- Django REST Framework 的 serializer 寫法太繁瑣
- API 文件要手寫或用插件,不是自動的
JVM 生態也有類似演化:Spring WebFlux(2017)是 Spring 的 reactive 版本,解決 Spring MVC 的 thread-per-request 在高並發下的問題;Kotlin 語言帶來 coroutine,Ktor 以此為基礎成為 Kotlin 原生的 async framework。
Minimal API 的回頭浪
2022 年之後有一個有趣的現象:輕量 framework 再次流行。
- Hono(2022,Node.js/Bun/Cloudflare Workers):比 Express 更快、typed route、跨 runtime
- Elysia(2023,Bun):利用 Bun 的效能,端對端型別安全
- ASP.NET Minimal API(.NET 6,2021):微軟把 .NET 的 API 寫法簡化,不用 Controller class
這波浪的驅動力是:NestJS / Spring Boot 這類 opinionated 框架,對小型 API service 來說太重了。如果你的 service 就是「接一個請求、查一次資料庫、回傳 JSON」,帶整個 Angular-inspired 架構進來是 overkill。
撞牆點(預測):minimal framework 在大型系統裡,還是會遇到「每個人結構不一樣」的問題。Meta-framework 和 convention-heavy 框架的下一波崛起,可能是因為 minimal framework 的 onboarding 問題又出現了。
演進的底層邏輯
每一代 framework 的出現都是因為:
- 前一代的解法在某個維度撞牆(效能、可維護性、開發速度、型別安全)
- 新的語言特性 / 語言生態創造了新的可能(GIL 的限制、async/await 的普及、TypeScript 的成熟)
- 部署模式改變(monolith → microservice → serverless → edge function)
這些技術不是「新的比較好」,而是「在不同的約束條件下,不同的 trade-off 更合理」。Express 在 2026 還活著,不是因為它沒有替代方案,而是因為它的 trade-off 在特定場景下仍然是最合理的——Express 為什麼還活著 有完整的分析。
