Lock-in 有幾種層次
把「被框架綁住」這件事分開來看,鎖定其實有不同的層次:
第一層:API 語法層
Express 的 (req, res, next) → NestJS 的 @Controller / @Get / @Body()。這層是最表面的 lock-in,改起來機械性很高——每個 route handler 都要改,但改法都一樣,技術上可以用 script 輔助。
第二層:生態選型層
你用了 express-session、express-rate-limit、express-validator。這些套件的 API 假設你在用 Express,換框架就要找對應版本或自己包裝 adapter。有的有一對一替代(rate limit 每個框架都有),有的沒有(某些 express-specific middleware 要重寫)。
第三層:架構思維層
這是最難的。如果你的 Express 系統已經有分層(controller / service / repository),遷 NestJS 相對容易——NestJS 的 Module 對應你的 service 邊界,Injectable 對應你的 service class。但如果你的 Express 系統是「所有邏輯在 route handler 裡」,遷 NestJS 等於同時重構架構和換框架,兩個困難的工作同時做。
遷移的真實成本
以一個 5 萬行的 Express + TypeScript 系統遷移到 NestJS 為例:
機械性工作(估計 30% 的工):
- 把
app.get('/path', handler)換成@Controller/@Get - 把
express-validator換成class-validator+ValidationPipe - 把手動的
require('service')換成 DI constructor 注入
架構工作(估計 50% 的工):
- 把散落在 route 裡的邏輯提取到 Service 層
- 把重複的 middleware 邏輯重組成 Guard / Interceptor / Pipe
- 把隱式的模組邊界用 NestJS Module 明確化
測試工作(估計 20% 的工):
- 重寫 supertest 的 integration test(因為 NestJS 的 server 建立方式不同)
- 更新 mock 方式(DI 的 mock 方式和手動 require 不同)
最大的隱性成本:認知切換。工程師要在還要繼續交付功能的同時,學習 NestJS 的 Module / Injectable / Guard 體系。知識不足的情況下做遷移,很容易做出「外觀是 NestJS 但本質還是 Express」的結果——@Injectable() 標了但還是在每個 service 裡 new OtherService()。
Layered Architecture 如何降低 Lock-in
最有效的降低 framework lock-in 的方法是:讓業務邏輯不知道 framework 的存在。
// ❌ 高度 lock-in:Service 依賴 Express 型別
class UserService {
async create(req: express.Request) {
const { name, email } = req.body; // ← 直接依賴 Express 的 Request
return db.create({ name, email });
}
}// ✅ 低 lock-in:Service 只接 plain DTO
class UserService {
async create(dto: CreateUserDto) { // ← 不知道 Express 的存在
return db.create(dto);
}
}
// Controller(薄層,負責 framework 轉換)
class UserController {
async store(req: Request, res: Response) {
const dto = { name: req.body.name, email: req.body.email }; // ← 轉換在 Controller
const user = await userService.create(dto);
res.json(user);
}
}如果你的 Service 層不知道 express.Request 存在,遷移時只需要改 Controller 層——Service 層不用動。
這正是 proto 的設計理念:app/services/ 裡的 class 不 import 任何 Express 型別,只接 DTO 和回傳 plain object。
什麼情況 Lock-in 代價值得付
不是每種 lock-in 都值得花力氣消除,要問:換掉的可能性有多高?換掉的收益有多大?
高可能性換掉 + 收益大:投資降低 lock-in(例如:ORM 選一個有 repository pattern 抽象的,讓底層 DB 可以換)。
低可能性換掉:接受 lock-in,不要過度抽象。如果你用 PostgreSQL 五年從來沒考慮過換,把所有 DB 操作包在抽象層後面只是增加複雜度。
Framework lock-in 的現實:換框架在大部分系統裡是低優先事項,因為框架只是你系統的外殼,業務邏輯才是核心價值。投資讓業務邏輯可測、可複用比投資讓它可換框架更有實際收益。
如果你真的要換
漸進式遷移比全部重寫風險低:
- 先建立新的 Service / Repository 層(不帶 framework 依賴的 business logic)
- 舊的 route handler 逐步委托給新的 Service(Controller 層薄化)
- 新的功能用新框架開發(不要在舊框架上繼續疊)
- 舊的 route 逐批遷移(每批上線後觀察一段時間再繼續)
這比「某週一全部切換」的風險小得多,而且每個階段都可以停下來評估:「繼續遷移的收益還值得投入嗎?」
