Worktile自上線兩年多以來,以良好的用戶體驗和穩定的服務,得到了用戶的承認和喜好。截止筆者寫這篇文章的時候,已經有超過10萬家團隊在使用Worktile。做爲團隊協做工具,從技術上分析首先要解決以下幾個問題:javascript
那麼Worktile是如何作到這幾點的?今天筆者在這篇文章裏一一爲你們揭祕。前端
SPA設計java
先來講說Worktile中SPA(單頁應用程序)設計,做爲團隊協做工具,須要儘量減小用戶在不一樣頁面之間的跳轉,因此從一開始咱們就決定Worktile必須是單頁應用程序,當時面臨的選擇有不少,首先咱們考慮使用大名鼎鼎的Backbone.js,可是很快又拋棄了,由於在實際使用中Backbone.js太複雜,另外一方面開發效率過低,最終咱們選擇了Google出品的AngularJs,下面這幅圖是AngularJS的結構圖:mysql
選擇它主要基於如下幾點考慮:web
1. 自動化雙向數據綁定功能,這一點在Worktile中很是重要,如任務的狀態變化都要實時變動到其餘成員,若是具備自動化雙向數據綁定功能,只須要綁定到UI的數據源發生變化,UI會自動發生改變,不須要工程師再經過代碼去修改UI元素的改變,以下面這段代碼:redis
<div class="entry-task-main" ng-class="{1:'task-completed-style'}[task.completed]"> <a class="entry-task-check" id="task_check_{{ task.tid }}" wt-click="js_complete_task($event, entry, task)"> <i ng-class="{0: 'icon-check-empty', 1: 'icon-check-sign'}[task.completed]"></i> </a> <a class="entry-task-title" href="javascript:;">{{task.name}}</a> </div>
2. 語義化標籤,AngularJS在設計之初信奉的理念就是:當編寫UI的同時又須要編寫業務邏輯時,聲明式的代碼遠比命令式代碼要好,命令式的代碼更適合寫業務邏輯,AngularJS在設計上就經過語義化的標籤把對DOM元素的操做和邏輯代碼分離,如咱們須要展示一個任務列表,只須要下面這段代碼便可:sql
<div ng-repeat="task in tasks"> <wt-task view-type="item" show-project="false" class="slide-trigger" hide_action="true" ng-click="locator.openTask(task.pid, task.tid)"> </wt-task> </div>
3. 模塊化設計,AngularJS堪稱模塊化設計方面的典範,經過模塊化設計咱們能夠很是好的實現Worktile的工程化,在Worktile中涉及的元素很是多,若有項目、任務、日程、文件、話題、文檔等等,而這每個元素均可以設計爲一個模塊,以下所示:mongodb
(function () { 'use strict'; angular.module('wtApp', [ 'wt.project.ctrl', 'wt.team.ctrl', 'wt.task.ctrl', 'wt.event.ctrl', 'wt.post.ctrl', 'wt.file.ctrl', 'wt.page.ctrl', 'wt.mail.ctrl' ]); }());
4. 引入依賴注入,依賴注入是面向對象中比較成熟的設計模式之一,爲了解決面向對象中依賴問題,獲得了普遍的應用,AngularJS中大膽使用了依賴注入,極大的減小了各個模塊之間的依賴問題:數據庫
taskListCtrl.$inject = ['$scope', '$stateParams', '$rootScope', '$popbox', '$location', '$timeout', 'bus', 'globalDataContext', 'locator'];
結合以上特色,咱們最終決定了前端框架使用AngularJS來實現,從Worktile上線兩年多的表現來看,咱們的選擇無疑是正確的。固然AngularJS也有一些缺點,在實際使用中仍是要根據具體的產品類型來選擇使用,另外AngularJS 2.0也已經初見端倪,和AngularJS 1.0有很大的不一樣,感興趣的同窗能夠先去嚐鮮一下。後端
服務設計
咱們再來看看Worktile的後臺服務設計,Worktile的總體服務架構設計以下圖所示:
其中前端部分在上面的SPA一節中咱們已經說過了,下面一一分析下其餘的服務:
1. API服務,包括Web API、Mobile API、Open API,這些都運行於NodeJS之上,選用NodeJS的緣由主要是它的異步事件驅動,對於高併發的支持比較好,另一個緣由是使用簡單,對於先後端可使用同一門語言去開發。
2. 緩存和隊列服務,Worktile中的緩存和隊列服務都是基於Redis來實現,Redis是一款很是優秀的開源緩存服務,而且能夠選擇基於內存仍是進行數據持久化,它提供的pub/sub模型對於Worktile來講很是重要,對於一些實時性要求不高的處理,咱們都是在Redis中pub一條消息,告知其餘服務有數據發生了變化,那些服務在接收到Redis中的消息後,根據消息的類型決定應該如何作出處理。
3. 數據庫服務,Worktile產品自己的特色決定了它是一個對實時性和性能的要求,遠超過對事務性要求的產品,因此在選擇數據庫時,咱們選用了MongoDB數據庫,性能高,集羣方便,數據以BSON結構存儲,和Node.js天生完美結合。
4. 文件預覽服務,使用Worktile的同窗確定知道在Worktile中全部的文件均可以作到無需下載到本地,而直接在線查看,這一切都是預覽服務的功勞,由於文件類型的各類各樣,在實現文件預覽時也要根據文件的類型作出不一樣的處理,針對txt、pdf、代碼片斷等文本型的文件,咱們只須要讀取文件中的內容,而後再前端用相應的視圖展示出來便可,相對比較簡單。可是對於Office類型的文件,如ppt、doc、xls等文件,就不能這麼簡單的處理,咱們但願文件在Worktile中查看的效果和用戶在本地使用Word、Excel、PowerPoint查看的效果差很少,通過咱們的調研,最終選用了微軟官方提供的Office Web App服務。
消息推送
消息推送服務是Worktile最核心的服務之一,前面提到過做爲一款團隊協做工具,要可以實現很是好的實時性,任何數據的變化都須要及時變動到團隊全部成員當前所在的視圖,以下面這幅圖,是一個典型的任務看板,團隊全部成員可能同時在操做當前項目中的任務,每一個操做引發看板的變化都會實時更新,不須要用戶作任何刷新操做:
爲了達到這種效果,須要在Web客戶端和服務器之間維持一個長鏈接,當有任何改變發生時,給客戶端發送不一樣的消息,告知客戶端哪些數據發生了變化,以下面是咱們爲任務定義的消息中的其中幾個:
實現實時消息推送,有如下幾種方式可供選擇:
on_task_trash : "on_task_trash", on_task_complete : "on_task_complete", on_task_move : "on_task_move", on_task_update : "on_task_update", on_task_comment : "on_task_comment", on_task_badges_file : "on_task_badges_file", on_task_unarchived : "on_task_unarchived", on_task_badges_check : "on_task_badges_check"
1. 短輪詢,頁面端經過js定時異步刷新,這種方式優勢在於實現簡單,但實時效果較差。
2. 長輪詢。頁面端經過js異步請求服務端,服務端在接收到請求後,若是該次請求沒有數據,則掛起此次請求,直到有數據到達或時間片(服務端設定)到,則返回本次請求,客戶端接着下一次請求,這種方式對於服務的要求較高,尤爲在併發量很大的狀況下,對服務端的壓力很大。
3. Websocket。瀏覽器經過websocket協議鏈接服務端,實現了瀏覽器和服務器端的全雙工通訊。須要服務端和瀏覽器都支持websocket協議。
在Worktile一開始咱們選用了Socket.IO做爲消息服務,可是隨着訪問量的增大,須要作集羣化的時候感受到力不從心,尤爲對於Socket.IO狀態數據的存儲,因爲並無官方的解決方案,當時咱們採用了一個第三方的開源項目,使用Redis來存儲,引發了一些性能上的問題,在後來重構時選用了基於Erlang語言的開源XMPP服務ejabberd做爲咱們的消息服務。
ejabberd是xmpp協議的一種實現, xmpp普遍應用於即時通訊領域。Xmpp協議的實現有不少種,好比java的openfire,但相較其餘實現,ejabberd的併發性能無疑使最優秀的。Xmpp協議的前身是jabber協議,早期的jabber協議主要包括在線狀態(presence)、好友花名冊(roster)、IQ(Info/Query)幾個部分。如今jabber已經成爲rfc的官方標準,如rfc2799, rfc4622, rfc6121,以及xmpp的擴展協議(xep)。Worktile就是基於XEP-012四、XEP-0206定義的BOSH擴展協議。
因爲自身業務的須要,咱們對ejabberd的用戶認證和好友列表模塊的源碼進行修改,經過redis保存用戶的在線狀態,而不是mnesia和mysql。另外好友這塊咱們是從已有的數據庫中(mongodb)中獲取Worktile中項目或團隊的成員。Web端經過strophe.js來鏈接(http-bind),strophe.js能夠以長輪詢和websocket兩種方式來鏈接,因爲ejabberd尚未好的websocket的實現,就採用了BOSH的方式模擬長鏈接。整個系統的結構以下:
後記
關於Worktile整個的技術架構就揭祕到這裏,經過上面的介紹,相信你們也能看到Worktile自己就是典型的MEAN(MongoDB、Express、AngularJS、NodeJS)架構,外加上ejabberd做爲實時消息推送服務,建議你們在本身的產品中,根據產品自身的特色和團隊的技術背景,來選擇具體使用哪一種技術。