目標
應用程序需要有個明確目標,能幫干活能解決問題。這一點同樣可以用于你決定去做的每件事上。目標明確根基夯實,應用程序才會越做越好。明確的目標會幫你闖出出一條如何正確解決問題的道路、也讓你在遇到阻礙陷入困境無法前行的時候依然堅定執著著望向遠方。。。
架構
架構會考慮到源代碼的設計,文件排列,程序庫/模塊使用,他們交織在一起編織出了應用程序。架構形式會隨著應用程序的開發不斷發生變化,它可能是一個web應用服務器,像express這樣的處理靜態資源(比如圖片),也可能是一個scheduler/worker pipeline,排隊以及處理隊列中的項。
無論何種目的,都有一些通用的原則應該遵守。
- 模塊化。盡量保證你的代碼遵守DRY原則(Don't Repeat Yourself(不要重復自己))。如果你發現你需要在許多不同的地方使用一段相似的代碼,通常應該將他們放在一個單獨的文件(或模塊)的一個函數中,從而形成通用幫助函數集模塊。這個模塊可以在其他依賴它的地方使用require()函數來加載。這樣做的目的不僅僅是防止多次重寫相似的功能,并且當你升級功能時,你只需要修改同一個函數即可。
- 遵守Node規范,盡量保證第三方node模塊放在node_modules文件夾下。同時也應該保證node_modules在你的.gitignore里,這樣你就不會提交不相干的依賴文件。
- 分離關系。前端相關的內容(靜態CSS,javascript,HTML,模板文件,圖片,資源文件)應該與后端應用邏輯(路由,服務器,中間件)分離。同樣的,應該將部署腳本,配置文件,數據關系文件和測試文件分開放置。
部署
你應用到生產環境的方法很大程度上依賴于你堆棧的性質。以下是我們嘗試過的一些方法:
- 手動通過SSH傳到服務器和拷貝git倉庫。優點:全手動控制,零部署工具設置。缺點:大量服務器時難以實施。所有的事情都需要手動設置,因此你不會得到任何好處,像upstart / initrc supervisation或者沒有日志的運行。
- Capistrano.優點:對于團隊開發者來說是標準的流程,只需簡單的運行:cap deploy。缺點:難以配置,需要Ruby依賴。
- Chef 腳本. 優點:通過腳本安裝程序。缺點:每次想部署時,需要重啟服務器。Chef最常被用于服務器的安裝/配置,而不是應用程序部署。
- Deliver. 當我們厭倦了其他的一些操作時,我們可以使用GoSquared出品的這個部署工具。
它的靈感來自于Heroku’s的git推送系統的基礎部署。你只需要為應用程序配置一個系統用戶(那些你需要做的——我們都幫你自動做好了),建立一個基本的分發配置,在工程里運行分發的命令行。它使用git通過SSH把應用程序推送到你的服務器,可以使用 foreman或者equivalent來監聽應用程序的啟動,復位和恢復。
這并不是一個詳盡的部署方案的列表,你可能需要發揮一點創意來構想一個最適合您自個需求的解決方案。無論你采用何種策略,將部署配置文件納入您的應用程序的源代碼版本控制并將部署流程記錄在您的README文件中都是一個好主意。
配置
幾乎每個應用程序都有一些常量和設置需要能被方便地更改。常見的有主機名,端口號,超時時間,模塊選項和錯誤。在一個地方保存這些值將非常有利,可存在一個文件或多個文件中,如果這些值足夠多。這樣做可以使他們能更快地被修改,而不必花時間在梳理代碼以跟蹤查找到它們。
我之前只是轉儲配置的設置到一個輸出配置屬性為對象的文件。這在一個非常特殊的環境下運行良好,比如在生產環境的時候,但隨著時間的推移,這開始成為維護的一個瓶頸,變得沒有條理性,并且在應用改變時產生了多個文件作為一個基礎設施。
我們可以通過環境配置獲得想要的結果,這個想法是指你可以在應用程序運行時修改基于環境的配置值。這個方式很簡單,輸出一個叫做$NODE_ENV 的shell環境變量包含一個你將要運行程序的環境模式標識。你的應用程序將定制配置的設置,當它啟動時使用你定義好的環境。
環境方面的配置為你提供了貫穿整個應用程序生命周期的更多的靈活性,你應該可以在本地開發和運行你的應用程序,而不需要網絡連接(你想要在火車上能夠進行?事實上寫這篇文章時我正好在火車上),這需要為本地服務指定主機地址和端口號。然后,你可能會希望在部署到服務器之前能夠進行測試。這些都需要不同的配置。
我們一般使用 node-config,一個為工作精確設計的模塊。所有你需要做的是在config/default.js中定義配置的值,接下來為你不同的環境建一個文件,包含繼承了default.js的默認指令。你設置$NODE_ENV作為環境變量的名字,這個模塊會覆蓋defaults.js中定義的[$NODE_ENV]屬性。導入到你的應用程序合并配置對象,你只需使用require()就可以。
日志,度量和監測
你想給自己足夠的應用的不正常行為的證據,以至于你就可以在盡可能很少的時間內使‘b0rked’變為”所有錯誤已經修正“。一個最好的辦法是依賴日志。通常的前提是,如果得到了錯誤,記錄它。你必須遵從node的錯誤處理規則,在Callback的第一個章節已經預設了當一個錯誤發生時的錯誤信息:
makeRyanDahlProud(function(err, result){
if(err){
console.log(err);
}
});
不管怎樣,如何應對錯誤完全在于你。你或許想記錄錯誤,然后繼續。或者你想終止執行這個回調。無論如何,你應該在未來應用這些錯誤,而且記錄日志是最簡單的實現方式。
盡管記錄錯誤日志是一個很好的方法,但是也會導致大量的消息被發送到logs日志或者終端。TJ Holowaychuck開發了一個叫做 debug的模塊,允許為日志消息創建命名空間,通過這途徑,我們就可以過濾掉不必要的消息,而通過命名空間抓取我們需要的消息。TJ的 repertoire還有大量其他模塊。
指標
應用指標為我們提供了有用的信息,以便查看應用在做什么操作和這些操作的時間間隔。它給我們提供一個重要的途徑去檢測異常的事件,瓶頸還有擴充收縮計劃的參考。我將這些歸結到一個小的模塊,叫做abacus,它可以幫助我們維護計數器的集合,并通過statsd把它們以可視化繪圖形式描繪到graphite。實踐證明,保證應用在給定的參數下運行時非常方便。
監測
監測并非是必需的,但是監測是一個很好的途徑,讓我們了解運行應用的服務器的資源使用率。另外的早期警告系統,同樣非常重要,可以讓我們避免一些低級問題而導致的應用崩潰。沒有比服務器硬盤空間不足而導致的應用崩潰,或者CPU使用超過負載而崩潰,而更讓人窘迫。
現在有大量的監測工具或者服務:開源的有Ganglia, Monit, Sensu ,還有SaaS服務ServerDensity, NodeTime and NewRelic 。
容錯
在部署的時候也需要考慮的,就是你還需要考慮如果你的應用崩潰后,將會發生什么。應用最好在系統監測程序下受控,例如Ubuntu的upstart。配置upstar十分瑣碎,但可以在應用崩潰時,處理開始,結束,和重啟,所以還是值得的。Foreman有一個導出功能,可以為你的foreman-backed應用生成upstar配置。
即使應用可以在崩潰后重啟,但是這樣做的含義是什么?這樣會讓服務在某些時候中止么?會丟失數據么?會留下一些為完成的任務么?一串問題你需要在設計時考慮,冗余是一個很好的解決方案。例如,你需要在多臺服務器之間傳輸數據,可以考慮增加一個反向代理(負載均衡,例如 HAProxy 或者web服務器,例如 nginx),從負載中移除應用中的不完善實例,直到健康檢查達到合格分數為止。
效率和可伸縮性
也許在開始階段我們很少考慮,但隨著你的應用的成長,事務壓力會不斷增大,你也許需要考慮讓應用變得更高效,甚至是可伸縮性。這里存在的風險是過早的優化。你不需要擔心讓你的應用擁有超強的可伸縮性或者擁有超級性能,因為在真正面對這么高的負載的之前,你為什么為此而苦惱呢?你還是把最寶貴的時間放在核心組件的構建上,或者"最小的可行性",就如,剛開始啟動,至少先到達需要擴展的階段。
當你的應用在單節點和單實例的情況已不能滿足需求的時候,你有幾個選擇,所有的權衡,就像在雷區中擺布一樣。首先要做的是,找出瓶頸。為什么應用會那么慢?是不是CPU占用率已達到頂峰值?是不是硬盤I/O不夠大或者是否足夠的連貫?
有時候最直接的答案是把應用遷移到擁有足夠資源的服務器上。如果這對于你來說,是可行的,那么任務就比在多節點上重新架構你的應用要簡單的多,但是如果應用增長速度很快,那這個可不是最好的解決方案。但是這個方法可能會消耗你大量的時間去構建。
在多個服務器上橫向擴展應用,是一個棘手的方案,并且引入大量的錯誤,但是長期來說卻帶來生命力和令人著迷的技術挑戰。
文檔和組內協作
一個應用如果沒有文檔,就像一個包裝盒沒有說明書一樣。你可以找出需要做什么,但這樣做是笨拙的,浪費時間和不精確的。最好你能提供清晰的文檔描述,簡要說明你的代碼。這樣做不單單是幫助其他人很快的上手和快速的開發,而且還能幫助你在六個月后重新拾起這個應用,修復臭蟲。
我并不主張像寫小說或者教程一樣去描述你的代碼。很多代碼可以自解釋,只要代碼足夠的簡潔。相反,你的文檔需要描述的是,代碼未能解釋的灰色地帶。注釋可以描述代碼的功能性,還有描述設計的理念,平衡,依賴,陷阱,邊緣例子和其他需要考慮的東西。隨著應用的成長,代碼的描述文檔也會增長,以反映應用變得越來越穩定的過程。在應用還處于初級階段的時候,沒必要太投入去編寫文檔。因為文檔在初期會經常改變,當你花了很多時間在它身上的時候,可能會很快又被廢除掉。
每個應用程序都應該包括一個README[MD],其中包含所有需要知道是應用程序正常工作的細節。通常這包括:
- 應用程序其目的的簡短說明
- 安裝說明
- 啟動說明
- 測試說明
- 如何部署
- 其他需要知道的點
我們需要一個模塊提前代碼里的注釋并生成干凈,有吸引力的文檔。我們會使用一個叫 docker 的模塊在我們的app中。
測試
以前我壓根沒有認識到測試有多么重要,也沒有被(QA)煩過。可能是因為我(太牛了)從來沒有遇到這樣悲催的情況:多年以后,之前開發的應用開始出問題了,你無法保證哪部分還正常工作,哪部分就快癱了。可是現在,經歷過這些狗血的事情后。我知道,我們需要測試,我們需要偉大的QA!!!
嚴格的測試可以造就好的應用。你需要考慮橫向地把應用切分為組件,而讓組件可以單獨的測試。這超越了基本的單元測試的迂腐,包含更多信息的組件和集成測試,可以讓你找到一個方法,以保證你的應用可以很好的集成在一起工作。
最好的方式是在你開發應用的時候編寫測試用例(一旦你有足夠的信心,你測試的功能不再有變化),那樣隨著應用的開發進程,測試也可以得到足夠的保證。這樣可以保證在開發新的功能的時候不會打斷現有的功能。測試同樣給其他開發者提供一個很好的例子,理解應用各個組件如何工作和結果是什么。
現在有大量的測試框架可供選擇,不同的技術( BDD, TDD). 我們嘗試過 vows 和 tap,但是我目前更喜歡用 mocha和should.js,因為我覺得使用它們,比使用純javascript,更能平衡架構和工具,同時允許使用同樣的代碼庫,腳本文件,而且還能在你的測試用例中啟動服務器。
依賴
Node擁有一個強大的模塊系統和被稱為npm的包管理工具,npm能幫助你無縫的集成各種模塊到你的應用中。npm給你提供了一個非常豐富的開源模塊的目錄 索引,你可以找到你想要的。
一旦你知道你的應用需要什么組件(例如和redis數據庫溝通的是什么客戶端),明智的選擇是首先檢查現有的配置索引中的用戶級節點模塊。Node.js是黑客們中最喜歡的實驗工具,所以這里有很多模塊可供你嘗試。自然,這些組件的質量都不一樣,所以你需要了解哪些是好的模塊,哪些卻不是(名單:README, 測試,例子,作者),但是基本上可以提供給你所有你需要的。
英文原文:https://engineering.gosquared.com/10-vital-aspects-of-building-a-node-js-application
