如果你們Team Dev正在使用PHP,並考慮遷移到Node.js,這篇文章很適合你。本文並不探討從PHP移植到Node.js的細節,以及Node.js的基礎知識。而是涵蓋:決策制定、著手點的描述、編寫 Node.js 伺服器的深層次注意事項、以及部署策略。
為什麼遷移?
1stdibs 決定從 Apache/PHP 遷移到 Node.js+Express 有五個理由:
- 代碼更少
- 全棧式JS
- 開發人員幸福度更高
- 投入回報率
- 未來的最佳化
代碼更少
1stdibs基於面向服務體系架構(SAO),前端調用背景JAVA服務。這意味著需要同時維護前端模型,以及服務端PHP和用戶端JS模板。試想一下,如果可以擺脫PHP,就能夠統一前端展現與後台模型於一種語言:JavaScript(同時可以合并一些模板)。從維護的角度來看,這麼做代碼更簡潔,並且沒有重複邏輯。
同構JavaScript萬歲!
全棧式JS(及其優點)
整個開發棧使用一種語言很簡便。對開發人員來說,較少的環境切換使他們開心和高效。額外的好處是工具使用更簡單。相比之前使用Composer和npm兩個包管理器,現在只需要一個。儘管Composer很出色,由於nbp負責工具和用戶端管理,nbp總是必要的。一旦去掉所有的PHP代碼,nbp將成為僅有的包管理器。
開發人員樂意
我們要保證開發人員的技能集得到擴充、職業生涯不斷髮展,這一點很重要。對於JavaScript工程師而言,Node.js極具吸引力。能夠在服務端使用與用戶端相同的工具、風格和模式,是非常順手和高效的。此外,Node.js相當流行,在企業級開發上也得到了長足發展。Node.js是JavaScript工程師的必備技能。
投入回報率
我們在招聘優秀的JS工程師和培訓初級JS工程師方面花了大價錢。由於用戶端棧很複雜,我們需要進階JavaScript工程師。我們不再僱用PHP工程師,僅僅僱用了JavaScript工程師。我們的觀點是,為什麼不培養他們在服務端的技能呢?
未來的最佳化
長遠而言,我們打算把兩個龐大的應用分割成一系列獨立部署的小應用。這很容易通過Node.js、Express和nbp實現。理論上,PHP(比如使用Slim)可以做同樣的事。但我們非但得不到上述好處,還會搞得一團糟:在Apache/PHP上進行操作會更加複雜,基礎設施也會變得有些奇怪。
選擇架構
那個最終被我們用Node.js替換掉的PHP應用,主要有如下職責:
- 登入和授權
- 路由選擇和服務端模板引擎(服務HTML)
- 引導前端應用
- 代理服務(為了迴避CORS)
- 服務靜態資源(js,css,images)
這些就是我們需要替換掉的準系統。
我們嘗試過不少架構,Express令人歎為觀止(試一下我們評估過的spreadsheet)。任何未基於Express 的架構看起來都不靠譜。Express通俗易懂,並有良好的文檔。另外,可以招聘到正經培訓過Express的人。
我們添加了一些kraken的核心模組(express-enrouten用於路由選擇、lusca負責安全);此外,i18n-node提供國際化支援,模板引擎使用Swig(我們後來放棄了Swig。呵呵,開源軟體還是有風險的)。
我們考慮過全盤使用kraken,但是從原來的服務端php模板引擎Twig切換到Swig直截了當,還很快捷。此外,kraken裡面的Dust和i18n也不討人喜歡。
編寫伺服器
選好了架構,下一步該寫伺服器了。
使用Apache+PHP時,你不需要再寫一個伺服器。Apache本身就是伺服器,PHP是應用。如果使用Node.js,伺服器和應用是同一個。從Apache/PHP轉到PHP,你需要處理一些之前自然而然使用的功能,這一點很重要。使用Apache,你(或者系統管理員)設定管理員,在PHP應用裡完全不用關心Apache為你處理的那些事。Node.js卻以一種不同的方式來工作。
提供靜態檔案服務
毋庸置疑,提供靜態檔案服務是Apache的核心功能。Node.js與此不同,你要在應用中配置靜態檔案服務。幸運的是這很簡單,有良好的文檔說明,並且是在Express中實現的。
日誌
很多基本的Apache配置為你提供訪問日誌和錯誤記錄檔。使用Node.js時,估計你也猜到了,同樣需要在應用中配置。所幸很多優秀的開源軟體包使之變得很簡單。Morgan是一個基本的請求日誌記錄器,它配置簡單,允許你把日誌寫到輸出資料流(標準輸出裝置或檔案)。如果你需要把日誌寫到資料庫,或者有別的(更進階的)日誌需求,那就試一下winston吧。
代理
我們有一個基本需求:能夠代理傳輸用戶端ajax請求到後台服務。相比於處理CORS頭,代理所有來自相同域的請求要簡單得多。但你要想通過代理使用webpack-dev-server(正如我們所做的),就必須在Node.js應用中處理這一問題。http-proxy是一個簡單可靠的解決方案。
剩下的工作
除了上面提到的,還有一些列別的工作需要完成。我們從一個MVC應用談起,該應用基於 CodeIgniter(CI)架構,為一系列單頁應用提供服務。大部分工作就是移植:
- CI控制器移植到Express路由選取器和中介軟體(包括登入和認證)
- Twig模板引擎移植到Swig(這一步比較瑣碎)
- Service層資料訪問(以便正常啟動用戶端單頁應用)
上面並未列出CodeIgniter模型這一關鍵組件。事實上根本不用重寫PHP模型!太給力了!我們的用戶端應用使用Backbone模型。當然這要擴充Backbone.Model.sync,從而使之全域地工作在伺服器和用戶端。
部署
如果你的app規模較大,不應該一次性全部上線。可以通過漸進式部署的方式逐步上線。我們因此花費了好幾周。
漸進式部署的優點:
- 最小化bug範圍
- 每次在發布一部分路由及功能的同時,其他工程師可以正常進行開發
- 對進行中的功能開發和改進影響最小 — 新功能可以繼續發布(這可能導致重複的工作)
- 如果操作得當,可以快速復原到之前的服務
NGINX很不錯
該如何逐步上線呢?我們在眾多的伺服器中挑選了Nginx。
+----------+ http | |---> Apache/PHP request---->| Nginx | | |---> Node.js +----------+
Nginx允許你一次只“開啟”一個路由(如果發現情況不妙就關掉 — 正如我們多次遇到的),這給了你很大的自由度。我們也發現開啟路由的時候不用部署代碼,這很有協助。這在一周一次的發布計劃裡,為我們提供了一些迴旋空間。
不過有一個缺點,你需要確保用戶端代碼同時接受舊的Apache/PHP伺服器和新的Node.js伺服器提供的服務。這並不可怕,不過你要把舊伺服器上未最佳化的功能移植到新的伺服器。屏住呼吸去做吧(記得寫一個便利貼去清理你的技術債)。
總結
從頭到尾,整個移植工作大概花費了一年。這聽起來可能有點荒謬,不過這個時間表包括決策過程(比較匆忙)、基於Express寫一個滿足需求的核心架構、移植所有功能、逐步漸進式上線。此外,請記住,我們始終只有一兩個開發人員為之工作 — 並且是兼職。
如果你想嘗試一下,請謹慎考慮。你的團隊能否受益?你的整個組織能否受益?如果你來自商業組織,請記住商業需要持續運轉。你需要在商業目標和工程目標之間找到良好的平衡。
我們很高興嘗試過。
關於作者:未濟
關注C,PHP,MySQL,JAVA,數學,美劇(尤其是權利的遊戲)。 個人首頁 · 我的文章 · 10 ·