1. 程式人生 > >團隊協作工具Worktile技術架構揭祕

團隊協作工具Worktile技術架構揭祕

摘要:目前,已經有超過10萬家團隊在使用Worktile,作為一款團隊協作工具,從技術上需要解決哪些問題,如何解決,Worktile創始人兼CTO李會軍給你深度揭祕。

【編者按】本文節選自程式設計師電子刊7A封面專題“中國SaaS生態‘元素週期表’”,Worktile作為一款傑出的團隊協作工具,自上線兩年多以來,以良好的使用者體驗和穩定的服務,獲得了使用者的認可和喜愛,本文來自Worktile創始人兼CTO李會軍關於Worktile整個技術架構的揭祕分享。

以下為全文:

Worktile自上線兩年多以來,以良好的使用者體驗和穩定的服務,獲得了使用者的認可和喜愛。截止筆者寫這篇文章的時候,已經有超過10萬家團隊在使用Worktile。作為團隊協作工具,從技術上分析首先要解決如下幾個問題:

  • 1. 基於Web的跨平臺設計,讓使用者在任何地方都可以隨時通過瀏覽器訪問
  • 2. Web形態的產品要具有原生客戶端的體驗,如任務的拖拽等
  • 3. 具有高效的實時訊息系統,每個團隊成員在Worktile中所做的任何操作,都要實時在其他成員的客戶端中自動重新整理
  • 4. 服務要穩定,穩定壓倒一切

那麼Worktile是如何做到這幾點的?今天筆者在這篇文章裡一一為大家揭祕。

SPA設計

先來說說Worktile中SPA(單頁應用程式)設計,作為團隊協作工具,需要儘可能減少使用者在不同頁面之間的跳轉,所以從一開始我們就決定Worktile必須是單頁應用程式,當時面臨的選擇有很多,首先我們考慮使用大名鼎鼎的Backbone.js,但是很快又拋棄了,因為在實際使用中Backbone.js太複雜,另一方面開發效率太低,最終我們選擇了Google出品的AngularJs,下面這幅圖是AngularJS的結構圖:


選擇它主要基於以下幾點考慮:

1. 自動化雙向資料繫結功能,這一點在Worktile中非常重要,如任務的狀態變化都要實時變更到其他成員,如果具有自動化雙向資料繫結功能,只需要繫結到UI的資料來源發生變化,UI會自動發生改變,不需要工程師再通過程式碼去修改UI元素的改變,如下面這段程式碼:

<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元素的操作和邏輯程式碼分離,如我們需要展現一個任務列表,只需要下面這段程式碼即可:

<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中涉及的元素非常多,如有專案、任務、日程、檔案、話題、文件等等,而這每一個元素都可以設計為一個模組,如下所示:

(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-0124、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作為實時訊息推送服務,建議大家在自己的產品中,根據產品自身的特點和團隊的技術背景,來選擇具體使用哪種技術。