2012年5月14日 星期一

前端工程師也可以淡定的開發網站應用!RedTea Web Framework!

Standard
還記得,尚未投入 Node.js 前,一直覺得 Node.js 帶來了未來,讓我們可以用 JavaScript 同時開發 Web 前端(Front-end)和後端(Back-end),等到真的投入 Node.js 後,發現雖然事實的確是如此,但由於前端和後端應用所需要的背景知識不盡相同,開發模式和概念更是大異其趣,所以,雖然同樣是使用大家熟悉的 JavaScript 語言,但前端開發者仍然不見得能夠如願地來開發後端應用。

這樣的情況讓我想起有位來台灣工作的外國朋友,曾告訴過我一個多年前發生在他身上的笑話。故事是:
他原本是個美術方面的設計師,然後,但有一天老闆對他說:『你從現在起,去做開發程式的工作吧。』

他問:『為什麼?』

老闆回答:『寫程式是用英文寫,而你又會英文,不就可以寫嗎?』

是的,同樣道理,雖然對前端工程師而言,JavaScript 是最熟悉的程式語言,而 Node.js 又可以讓你使用 JavaScript 寫整個 Web 應用,但這不代表對這些人而言,就可以輕易上手 Web service 的後端開發。要真正讓前後端開發合而為一,不只是語言要統一,開發經驗也要相同才是,這才是所有 Web 開發者最期盼的事。

於是筆者基於 Node.js,開發了『紅茶(RedTea)』,這是一個和現有的網站框架(Web Framework)所不一樣的全新的嘗試。以前端開發者視角和經驗為出發點,專門設計給前端工程師上手使用的網站框架,讓前端工程師也可以『淡定』的開發網站應用。由於 RedTea 不是傳統 MVC 模型的 Framework(至於 RedTea 採用的是哪種開發模型,筆者一時也說不上來。),所以,如果你是一個已經被 MVC 涂毒已深的開發者,可能要先花點時間重新理解一下。:-)

此外,RedTea 有一個最大的特點,就是支援了在瀏覽器環境下,呼叫後端 JavaScript Class/Function 的功能,就像在使用本地端的 JavaScript 物件般。因此,前後台交換資料,不用再以 GET/POST、URL Path Routing 或 Ajax 相關的方法實作,只要學會怎麼使用 JavaScript Class 即可。重點是,即使你沒有任何 HTTP 通訊協定的知識,或後前後端資料處理的經驗,依然可以開發出網站程式。

當然,設計 RedTea 的目的,除了是為前端工程師著想之外,也是因為長久以來在思考 MVC 模型的問題後,所嘗試提出的解決方案。至於是什麼問題,可以從專案開發的流程探討,一個基於 MVC 模型的 Web 專案開發流程大致上如下(問題點也將補充在後面):
*註:『程式架構和演算法:Model』、『視覺部份和 UI:View』、『控制機制:Controller』
  1. 設計 Model(問題:當你一開始根本不完全清楚功能需求時,你如何能設計一個完善的架構去容下一切?)
  2. 設計 View
  3. 設計 Controller,用來連接程式邏輯架構和前端 UI (問題:有太多種交換方式,GET/POST Routing stuffs, Ajax APIs 等)
  4. 為了更完整的功能需求,一次又一次修改和重構 Model
  5. 為了配合程式架構和演算法的修改,一次又一次修改和重構 View
  6. Controller 零亂又噁心不已

說穿了,這種來來回回式的開發方法,若是在前後端是不同語言的情況下,將會是不得已的情況。畢竟,前後端需要共同定出一個雙方可以接受的資料交換格式,然後在開發過程中逐漸磨合。不過,當前後端都是 JavaScript 的情況下,是否可以簡化這部份的開發流程,就是一個可以思考的地方。

所以,RedTea 的設計,主要是為了達成這樣的 Web 開發流程:
  1. 設計你眼前第一時間想看到的東西
  2. 在所看到的 UI 上,實作功能需求
  3. 在後端(Server-side)處理使用者因需求產生的資料

由於前後端都是 JavaScript,很容易做到前後端邏輯上的同步,再加上簡單的轉換器和 RPC 設計,就可以讓前端程式直接呼叫後端的 Class 和 Function。後端的 API,就彷彿是你前端程式的一部份。

說了這麼多,到底如何使用 RedTea 開發出一個網站呢?如果你參考 Github 上的 RedTea 範例,會發現主要有四個目錄,分別說明如下:
  1. ui - Layout Template
  2. runner - Browser-side Script
  3. routes - URL Path Routing
  4. apis - Server-side APIs

若你有摸過其他的 MVC Web Framework,對 routes 的功用應該不陌生,而 RedTea 當然也可以像其他框架一樣,讓你隨意自定 URL 的橋接。但若要善用 RedTea 的優勢,routes 的主要目的應該只是決定 UI 和 Runner 的組合,如下:
module.exports = {
    '/': index
};

function index(app, req, res) {
    /* Using index.jade (UI) and index.js (Runner) */
    res.render('index', { title: 'RedTea' });
};

RedTea 在回應瀏覽器的要求時,會合併指定的 UI 和 Runner,並自動代入 RedTea Caller 的機制,讓 runner 被輸出到前端時,有能力呼叫 Server-side APIs。

一個 runner 程式大概長的是這個樣子(examples/runner/index.js):
RedTea.import();

RedTea.main(function() {
    var chat = new RedTea.API.Chat;

    chat.say('Fred', 'Hello World!');
    chat.getConversation(function(err, data) {
        for (var i in data) {
            var lineObj = data[0];
            var dom = document.getElementById('conversation');
            dom.innerHTML += lineObj.name + ':' + lineObj.content + '<br>';
        }
    });
});

從範例中,你會看到瀏覽器上才有的 document 物件出現,事實上 runner 就是一支跑在瀏覽器上的 JavaScript 程式。 RedTea.import() 會去 Server 載入目前已經被定義的 API,等初始化完成,會以 RedTea.main() 為程式進入點開始跑。這時,你就可以用 RedTea.API 去建立 Server API 定義的物件,然後呼叫在 Server-side 的函數方法。進一步,你也可以用得到的資料,去更新網頁上的 DOM。

至於 Server-side APIs 的定義與寫一般 JavaScript 無異,你可以定義 Class、Function 或是變數,RedTea 會自動將這些 API 轉換成前端可以跑的形式,而原始的 API 定義長這個樣子(examples/apis/chat.js):
module.exports = {
    Chat: Chat
};

function Chat(externalData) {
    this.externalData = externalData;
    this.publicData = externalData.userdata;
}

Chat.prototype.getUserList = function(callback) {

    callback(null, this.publicData.users);
};

Chat.prototype.getConversation = function(callback) {

    callback(null, this.publicData.conversation);
};

Chat.prototype.say = function(name, content) {
    var line = {
        name: name,
        content: content
    };

    this.publicData.conversation.push(line);
};

然後和一般的 Node.js 程式一樣,你會需要一支主程式(examples/app.js):
var RedTea = require('redtea');

var app = new RedTea;

app.routeDirs.push(__dirname + '/routes');
app.uiDirs.push(__dirname + '/ui');
app.runnerDirs.push(__dirname + '/runner');
app.apiDirs.push(__dirname + '/apis');

var publicData = {
    users: [],
    conversation: []
};

app
    .initRoute()
    .initRender()
    .initAPI(publicData)
    .listen(9876);


只會 JavaScript 嗎?開始享受單純用 JavaScript 開發網站應用程式的人生吧!卡關了?就喝杯紅茶淡定一下! :-D

後記

RedTea 目前只是原型,還不是相當完整,像是 static file 的功能都尚未實作,很多功能也還只是堪用,歡迎各界一同補齊它。 :-)