# 架構篇一: CMS的重構與演進
重構系統是一項非常具有挑戰性的事情。通常來說,在我們的系統是第二個系統的時候才需要重構,即這個系統本身已經很臃腫。我們花費了太量的時間在代碼間的邏輯,開發新的功能變得越來越慢。這不僅僅可能只是因為我們之前的架構沒有設計好,而且在我們開發的過程中沒有保持著原先設計時的一些原則。如果是這樣的情況,那么這就是一個復雜的過程。
還有一種情況是我們發現了一種更符合我們當前業務的框架。
### 動態CMS
### CMS簡介
CMS是Content Management System的縮寫,意為“內容管理系統”.它可以做很多的事情,但是總的來說就是Page和Blog——即我們要創建一些頁面可以用于寫一些About US、Contact Me,以及持續更新的博客或者新聞,以及其他子系統——通常更新不活躍。通過對這些博客或者新聞進行分類,我們就可以有不同的信息內容,如下圖:

不同分類的內容
CMS是政府和企業都需要的系統,他們有很多的信息需要公開,并且需要對其組織進行宣傳。在我有限的CMS交付經驗里(大學時期),一般第一次交付CMS的時候,已經創建了大部分頁面。有時候這些頁面可能直接存儲在數據庫中,后來發現這不是一個好的方案,于是很多頁面變成了靜態頁面。隨后,在CMS的生命周期里就是更新內容。
因而,CMS中起其主導的東西還是Content,即內容。而內容是一些持續可變的東西。這也就是為什么WordPress這么流行于CMS界,它是一個博客系統,但是多數時候我們只需要更新內容。除此不得不提及的一個CMS框架是Drupal,兩者一對比會發現Drupal比較強大。通常來說,強大的一個負作用就是——復雜。
WordPress和Drupal這一類的系統都屬于發布系統,而其后臺可以稱為編輯系統。
一般來說CMS有下面的特點:
- 支持多用戶。
- 角色控制-內容管理。如InfoQ的編輯后臺就會有這樣的機制,社區編輯負責創建內容,而審核發布則是另外的人做的。
- 插件管理。如WordPress和Drupal在這一方面就很強大,基本可以滿足日常的需要。
- 快捷簡便地存儲內容。簡單地來說就是所見即所得編輯器,但是對于開發者來說,Markdown似乎是好的選擇。
- 預發布。這是一個很重要的特性,特別是如果你的系統后臺沒有相對應的預覽機制。
- 子系統。由于這屬于定制化的系統,并不方便進行總結。
- …
CMS一直就是這樣一個緊耦合的系統。
### CMS架構與Django
說起來,我一直是一個CMS黨。主要原因還在于我可以隨心所欲地去修改網站的內容,修改網站的架構。好的CMS總的來說都有其架構圖,下圖似乎是Drupal的模塊圖

Drupal 框架
一般來說,其底層都會有:
- ORM
- User Management
- I18n / L10n
- Templates
我一直在使用一個名為Django的Python Web框架,它最初是被開發來用于管理勞倫斯出版集團旗下的一些以新聞內容為主的網站的,即是CMS(內容管理系統)軟件。它是一個MTV框架——與多數的框架并沒有太大的區別。
| 層次 | 職責 |
|-----|-----|
| 模型(Model),即數據存取層 | 處理與數據相關的所有事務:如何存取、如何驗證有效性、包含哪些行為以及數據之間的關系等。 |
| 模板(Template),即表現層 | 處理與表現相關的決定: 如何在頁面或其他類型文檔中進行顯示。 |
| 視圖(View),即業務邏輯層 | 存取模型及調取恰當模板的相關邏輯。模型與模板之間的橋梁。 |
從框架本身來上看它和別的系統沒有太大的區別。

Django Architecture
但是如果我們已經有多外模塊(即Django中app的概念),那么系統的架構就有所不同了。

Django App架構
這就是為何我喜歡用這個CMS的原因了,我的每個子系統都以APP的形式提供服務——博客是一個app,sitemap是一個app,api是一個app。系統直接解耦為類似于混合服務的架構,即不像微服務一樣多語言化,又不會有宏應用的緊耦合問題。
### 編輯-發布分離
我們的編輯和發布系統在某種意義上緊耦合在一起了,當用戶訪問量特別大的時候,這樣會讓我們的應用變得特定慢。有時候編輯甚至發布不了新的東西,如下圖引示:

發布-編輯
或者你認識出了上圖是源自Martin Folwer的[編輯-發布分離](http://martinfowler.com/bliki/EditingPublishingSeparation.html)
編輯-發布分離是幾年前解耦復雜系統游來開來帶來的一個成果。今天這個似乎已經很常見了,編輯的時候是在后臺進行的,等到發布的時候已經變成了一個靜態的HTML。
已經有足夠多的CMS支持這樣的特性,運行起來似乎特別不錯,當然這樣的系統也會有緩存的問題。有了APP這后,這個趨勢就更加明顯了——人們需要提供一個API。到底是在現有的系統里提供一個新的API,還是創建一個新的API。
這時候,我更愿意選擇后者——畢竟緊耦合一個系統總會在后期帶來足夠多的麻煩。而且基于數據庫構建一個只讀的RESTful API并不是一個復雜的過程,而且也危險。這時候的瓶頸就是數據庫,但是似乎數據庫都是多數系統的瓶頸。人們想出了各種各樣的技術來解決這個瓶頸。
于是之前我試著用Node.js + RESTify將我的博客重構成了一個SPA,當然這個時候CMS還在運行著。出于SEO的原因我并沒有在最后采用這個方案,因為[我網站](https://www.phodal.com)的主要流量來源是Google和是百度。但是我在另外的網站里混合了SPA與MPA,其中的性能與應用是相當的,除了第一次加載頁面的時候會帶來一些延時。
除了Node.js + RESTify,也試了試Python + Falcon(一個高性能的RESTful框架)。這個API理論上也應該可以給APP直接使用,并且可以直接拿來生成靜態頁面。
### 編輯-發布-開發分離:靜態站點生成
如React一樣解決DOM性能的問題就是跳過DOM這個坑,要跳過動態網站的性能問題就是讓網站變成靜態。
越來越多的開發人員開始在使用Github Pages作為他們的博客,這是一個很有意思的轉變。主要的原因是這是免費的,并且基本上可以保證24x7小時是可用的——當且僅當Github發現故障的時候才會不可訪問。
在這一類靜態站點生成器(Github)里面,比較流行的有下面的內容(數據來源: [http://segmentfault.com/a/1190000002476681](http://segmentfault.com/a/1190000002476681)):
1. Jekyll / OctoPress。Jekyll和OctoPress是最流行的靜態博客系統。
1. Hexo。Hexo是NodeJS編寫的靜態博客系統,其生成速度快,主題數量相對也比較豐富。是OctoPress的優秀替代者。
1. Sculpin。Sculpin是PHP的靜態站點系統。Hexo和Octopress專注于博客,而有時候我們的需求不僅僅是博客,而是有類似CMS的頁面生成需求。Sculpin是一個泛用途的靜態站點生成系統,在支持博客常見的分頁、分類tag等同時,也能較好地支持非博客的一般頁面生成。
1. Hugo。Hugo是GO語言編寫的靜態站點系統。其生成速度快,且在較好支持博客和非博客內容的同時提供了比較完備的主題系統。無論是自己寫主題還是套用別人的主題都比較順手。
通常這一類的工具里會有下面的內容:
1. 模板
1. 支持Markdown
1. 元數據
如Hexo這樣的框架甚至提供了`一鍵部署`的功能。
在我們寫了相關的代碼之后,隨后要做的就是生成HTML。對于個人博客來說,這是一個非常不錯的系統,但是對于一些企業級的系統來說,我們的要求就更高了。如下圖是Carrot采用的架構:

Editor Develoepr
這與我們在項目上的系統架構目前相似。作為一個博主,通常來說我們修改博客的主題的頻率會比較低, 可能是半年一次。如果你經常修改博客的主題,你博客上的文章一定是相當的少。
上圖中的編輯者通過一個名為Contentful CMS來創建他們的內容,接著生成RESTful API。而類似的事情,我們也可以用Wordpress + RESTful 插件來完成。如果做得好,那么我想這個API也可以直接給APP使用。
上圖中的開發者需要不斷地將修改的主題或者類似的東西PUSH到版本管理系統上,接著會有webhook監測到他們的變化,然后編譯出新的靜態頁面。
最后通過Netlify,他們編譯到了一起,然后部署到生產環境。除了Netlify,你也可以編寫生成腳本,然后用Bamboo、Go這類的CI工具進行編譯。
通常來說,生產環境可以使用CDN,如CloudFront服務。與動態網站相比,靜態網站很容易直接部署到CDN,并可以直接從離用戶近的本地緩存提供服務。除此,直接使用AWS S3的靜態網站托管也是一個非常不錯的選擇。
### 基于Github的編輯-發布-開發分離
盡管我們已經在項目上實施了基于Github的部分內容管理已經有些日子里,但是由于找不到一些相關的資料,便不好透露相關的細節。直到我看到了《[An Incremental Approach to Content Management Using Git 1](https://www.thoughtworks.com/insights/blog/incremental-approach-content-management-using-git)》,我才意識到這似乎已經是一個成熟的技術了。看樣子這項技術首先已經應用到了ThoughtWorks的官網上了。
文中提到了使用這種架構的幾個點:
1. 快速地開始項目,而不是學習或者配置框架。
1. 需要使用我們信奉的原則,如TDD。而這是大部分CMS所不支持的。
1. 基于服務的架構。
1. 靈活的語言和工具
1. 我們是開發人員。
So,so,這些開發人員做了些什么:
1. 內容存儲為靜態文件
1. 不是所有的內容都是平等的
1. 引入內容服務
1. 使用Github。所有的content會提交到一個repo里,同時在我們push內容的時候,可以實時更新這些內容。
1. 允許內容通過內容服務更新
1. 使用Github API
于是,有了一個名為[Hacienda](https://github.com/haciendaio/hacienda)的框架用于管理內容,并存儲為JSON。這意味著什么?

基于Github的編輯-發布-開發分離
因為使用了Git,我們可以了解到一個文件內容的歷史版本,相比于WordPress來說更直觀,而且更容易 上手。
開發人員修改完他們的代碼后,就可以直接提交,不會影響到Editor使用網站。Editor通過一個編輯器添加內容,在保存后,內容以JSON的形式出現直接提交代碼到Github上相應的代碼庫中。CI或者Builder監測到他們的辦法,就會生成新的靜態頁面。在這時候,我們可以選擇有一個預覽的平臺,并且可以一鍵部署。那么,事情似乎就完成得差不多了。
如果我們有APP,那么我們就可以使用Content Servies來做這些事情。甚至可以直接拿其搭建一個SPA。
如果我們需要全文搜索功能,也變得很簡單。我們已經不需要直接和數據庫交互,我們可以直接讀取JSON并且構建索引。這時候需要一個簡單的Web服務,而且這個服務還是只讀的。
在需要的時候,如手機APP,我們可以通過Content Servies來創建博客。
### Repractise
> 動態網頁是下一個要解決的難題。我們從數據庫中讀取數據,再用動態去渲染出一個靜態頁面,并且緩存服務器來緩存這個頁面。既然我們都可以用Varnish、Squid這樣的軟件來緩存頁面——表明它們可以是靜態的,為什么不考慮直接使用靜態網頁呢?
思考完這些后,我想到了一個符合學習的場景。

基于Travis CI的編輯-發布-開發分離
我們構建的核心都可以基于Travis CI來完成,唯一存在風險的環節是我們似乎需要暴露我們的Key。
### 其他
參考文章:
1. [靜態網站生成器將會成為下一個大熱門](http://www.infoq.com/cn/news/2015/11/LAMP-CDN)
1. [EditingPublishingSeparation](http://martinfowler.com/bliki/EditingPublishingSeparation.html)
1. [An Incremental Approach to Content Management Using Git 1](https://www.thoughtworks.com/insights/blog/incremental-approach-content-management-using-git)
1. [Part 2: Implementing Content Management and Publication Using Git](https://www.thoughtworks.com/insights/blog/implementing-content-management-and-publication-using-git)