老web換新枝----Sails.js移動設備的全新生產力(三)

    Sails.js 是通過 JavaScript 對象表示的,這些對象可存儲在任何類型的數據存儲中 — 關係、面向文檔或其他存儲。在 Sails.js 中開發您的第一個模型,然後開始使用 Sails 藍圖,後者提供了對數據驅動的應用程序無處不在的創建、讀取、更新和刪除功能的內置支持。

現在我們將撇開 “Hello World” 應用程序的簡單樂趣,創建一個更加真實的示例應用程序。

    應用程序是一個博客 API,其中集成了傳統 REST 架構的部分元素。該博客應用程序將保存併發布博客條目,還將執行典型的博客操作,比如跟蹤評論,生成和顯示 RSS 提要等。HTTP API 設計有助於建立一個穩定的可擴展後端,而視圖取決於移動、桌面或 Web 客戶端。

博客 API 擁有相對較小的用例集合,至少在開始的時候是這樣。用戶能夠創建任何數量的博客條目,而且每個條目都將包含標題、正文、標記和作者。這些條目還將顯示一個創建時間戳和另一個更新時間戳。

在以後,您將重構該應用程序並添加更多的功能,但現在我們將保持它的簡單性。

建模該應用程序

關於本系列

    Sails 爲構建 HTTP API 提供了很好的開箱即用的支持。這種設計有助於在客戶端和服務器之間實現更好的關注點分離,並促進客戶端和服務器之間的更多的互操作性。在本系列中,Web 開發人 員和教師 Ted Neward 將向您介紹 Sails.js。HTTP API 示例應用程序可以是 AngularJS、React 或幾乎其他任何客戶端應用程序的後端。

    從頭開發 MVC 應用程序時,您可能會考慮應該從模型還是從控制器元素開始。在必要時,不僅可以從控制器開始,也可以從控制器構建完整的應用程序:您只需爲應用程序的每個功能定義一個控制器,並與數據庫傳輸數據。您始終可以在以後重構該應用程序,從您的控制器架構中提取模型。

   在 Sails.js 中,從模型開始的效率更高一些。不僅 Sails 模型容易設計,Sails 還提供了一系列稱爲藍圖 的控制器路由,它們會自動應用於不同的模型類型。您需要做的只是在 Sails 中定義模型,Sails 將自動爲該模型的基本數據存儲和檢索功能設置 API 路由和功能。

    Sails.js 的模型優先方法也非常適合 HTTP API 應用程序的 RESTful 設計。REST 希望公開資源,您已在模型中提前定義了這些資源。

    爲博客 API 建模很容易:只需定義應用程序所需的模型類型,Sails 默認情況下會爲它們建立藍圖。在擁有模型和藍圖後,還要定義它們之間的關係,並添加一些獨立控制器來處理整個應用程序的行爲。對於該博客 API,我們需要一個獨立控制器來查找評價最高的博客條目,還需要另一個控制器來提供博客條目的 RSS 提要。

一般化的建模

    發第一個 Sails 模型之前,需要確定如何 建模您的數據:您將使用關係實體、文檔實體還是其他實體?在 Sails 中,答案是視具體情況而定。

     正常情況下,在使用其他軟件堆棧(比如 MEAN 堆棧)時,數據存儲引擎是堆棧的重點部分。因此,您使用該堆棧爲應用程序定義的條目是根據數據存儲引擎自己的偏好嚴格建模的。例如,在使用 Rails 時,您將構建與關係實體緊密關聯的模型;使用 MEAN 時,您將構建面向文檔的模型。

    不同於其他一些堆棧,Sails 從數據存儲引擎中抽象出數據操作。因此,您可以選擇 MySQL 和 Postgres 等關係數據庫,MongoDB 等文檔數據庫,Redis 等鍵值存儲,甚至可以選擇內存型或自定義的簡單磁盤存儲格式。此抽象在數據存儲引擎的選擇上爲您提供了很高的靈活性,但也爲您留下了一些一般性選項。(這是功能還是限制完全取決於您的決定。)

記住差異:ORM 和 ODM

對象關係映射 (ORM) 是開發人員用於減輕面向對象的編程與關係數據存儲之間的上下文差異(或阻抗失配)的工具。如果您是一名 Java 開發人員,您或許使用過或聽說過 Hibernate 和 Java Persistence API 等 ORM 工具。如果使用 .NET 作爲平臺,那麼您可能知道 Entity Framework。

就像使用關係數據存儲的開發人員需要解決對象和關係之間的阻抗失配一樣,設置面向文檔的數據存儲的開發人員需要解決對象和文檔之間的差異。Mongoose(用於 MongoDB)等對象文檔映射 (ODM) 工具可幫助解決這個問題。

可能想知道何時將使用對象分層映射器 (OHM) 來處理分層系統,比如 XML 數據庫。以及在 OHM 出現時,我們是否會面對一片抗議聲?

Waterline:Sails ORM

    Sails 抽象數據層的核心是它的內置 ORM,名爲 Waterline。作爲一個數據訪問層,Waterline 支持幾乎所有數據庫,包括關係和非關係 (NoSQL) 數據庫。儘管 Waterline 對任何類型數據存儲的支持滿足了避免供應商鎖定的訴求,但一般化也消除了針對特定類型的數據層進行優化的機會。

    幸運的是,您不需要 結合使用 Waterline 和 Sails。所以如果您認爲 Waterline 不適合針對 MongoDB 的數據建模,那麼您可以悄悄使用 Mongoose。然後,您可以直接將 Sails 應用程序與 MongoDB 和 Mongoose 綁定在一起。

考慮到這項提醒,我們暫時假設您在爲一個一般化的數據層建模。

基於磁盤的持久性

    在開發週期的早期,您或許不想太多地考慮持久性層,甚至是像您從 MongoDB 獲得的一般化且鬆散類型的持久性層一樣。這意味着除了堅持採用 Waterline 之外,您將爲 Sails 使用默認的持久性層。這是一個純粹的本地系統,Sails 稱之爲 “sails-disk” 數據庫,它將對象存儲在內存中,直到可以將它們寫入磁盤。實際存儲格式非常簡單,但幾乎不相關,因爲您只能將它用於設計項目原型,自行實驗或在文章中演示 Sails。使用默認系統爲您提供了以創造性的方式思考模型的最終狀態的空間,但又不會讓您太快地迷失在構建這種複雜東西的細節上。因爲沒有模式,所以在您準備好後執行重構將會非常容易。

    創建 Sails 模型的過程是一種類似對象的體驗,但不是您可能熟悉的富域模型。Sails 模型缺少基於對象的編程的一些常見功能。但是,如果您瞭解其他框架中的對象關係映射,就可能發現 Sails 與其中的體驗差不多。在開始建立對象之間的關係時,您將面臨它們之間的權衡。就現在而言,我們首先會定義一些基本的數據類型。

建模----一個博客條目

    花點時間想想您希望在博客條目中看到哪些功能。您不需要立即規劃和實現每個組件,但在開始編碼之前制定一個基本計劃是一個不錯的想法。作爲一個模型,   BlogEntry 至少需要一個標題和正文。因爲人們經常編輯和檢索博客條目,所以一個不錯的想法是添加一個 created 字段來表示首次創建該條目的時間,添加一個 updated 字段來表示最後編輯時間。

在以後添加 tags 和 author 等其他字段。我們現在首先來查看前 4 個字段。

Sails:生成!

您已選定了一個基本模式並決定讓 Sails 將模型存儲到磁盤中。接下來,您將告訴 Sails 生成您的數據模型和關聯的控制器的腳手架。首先調用命令 sails generate api BlogEntry。該命令將創建兩個文件:BlogEntry.js(可在 /api/models 目錄中找到)和BlogEntryController.js(在 /api/controllers 目錄中)。

我們將 BlogEntryController 留到以後討論。現在來看看 BlogEntry 模型:

1. 一個空模型

/**

 * BlogEntry.js

 *

 * @description :: TODO: ...

 * @docs        :: http://sailsjs.org/#!documentation/models

 */

module.exports = {

  attributes: {

  }

 };

型顯然需要一些屬性。使用您的默認 sails-disk 數據庫,首先定義 BlogEntry 類型。請注意,您沒有添加 created 和 updated 屬性,因爲 Sails 會自動添加它們:

2. 將字段添加到模型中

/**

 * BlogEntry.js

 *

 * @description :: A blog entry stored in the CMS.

 * @docs        :: http://sailsjs.org/#!documentation/models

 */

module.exports = {

  attributes: {

    title: {

      type: 'string'

    },

    body: {

      type: 'string'

    }

  }


};


接下來,Sails 會定義一個基於 BlogEntry 模型的 BlogEntry 類(任何種類;位於所有 JavaScript 後)。該模型的每個實例都將擁有您在它之上定義的初始博客條目屬性。

將屬性添加到模型中

    Sails 支持的屬性遠不止字符串類型,還包括一些方便的標準,比如'string''text''integer''float''date''datetime''boolean''binary''array' 和 'json'。其中許多屬性也支持依據需求執行自定義(例如用於驗證或實現唯一性)。

碰巧, 2 中的模型缺少一些關鍵內容。如果兩篇博客文章擁有同一個標題會怎樣?這是一個完全合理的假設場景,而且很容易通過向模型添加主鍵字段來管理。這聽起來像 'integer' 屬性的工作,如下所示。

3. 將主鍵添加到模型中

/**

 * BlogEntry.js

 *

 * @description :: A blog entry stored in the CMS.

 * @docs        :: http://sailsjs.org/#!documentation/models

 */

module.exports = {

  attributes: {

    id: {

      type: 'integer'

    },

    // ... the rest

  }

};

    這是 Waterline 模型管理不同數據存儲的方法變得有趣的地方,因爲 Waterline 必須能夠向數據庫描述數據模型中的任何屬性。例如,Waterline 需要知道該 ID 是否將是主鍵。如果它是(大部分情況下是這樣),Waterline 會將 RDBMS 中的 id 列標記爲 PRIMARY KEY,將 MongoDB 中的一個 id 屬性改爲 _id,等等。

Sails 允許您使用 JavaScript 的簡單的對象式語法來提供元屬性,比如:

4. 將元屬性添加到模型中

/**

 * BlogEntry.js

 *

 * @description :: A blog entry stored in the CMS.

 * @docs        :: http://sailsjs.org/#!documentation/models

 */

module.exports = {

  attributes: {

    id: {

      type: 'integer',

      primaryKey: true

    },

    title: {

      type: 'string',

      required: true,

      defaultsTo: ''

    },

    body: {

      type: 'string',

      required: true,

      defaultsTo: ''

    }

  }

};

這是一種非常直觀的描述格式。轉換爲人類語言後,上述代碼將類似於:id 是一個整數類型的屬性,primaryKey 元屬性被設置爲 true。

以下是 Sails 中使用的常見元屬性的快速參考:

· email 驗證數據元素是否是電子郵件地址。

· 如果未指定值,defaultsTo 會將該值設置爲 use。

· autoIncrement 只能用在整數屬性上。

· unique 驗證此屬性是否在所有實例上是唯一的。

· primaryKey 表明該屬性將用作模型的主鍵。

· enum 表明這個元屬性的值是隻接受給定屬性的值的數組。所以 enum:['Fred', 'Barney', 'Wilma', 'Betty'] 表示僅接受這 4 個值。

· size 將會設置傳遞給數據庫的大小限制,通常是字符串長度限制。

請參閱 Sails 或 Waterline 文檔,瞭解 Sails 元屬性的完整列表。

上面的簡單模型對基本的博客引擎已足夠了。保存您的 BlogEntry.js 文件,然後執行 sails lift 在本地運行它。嚴肅地講,請完成這一步後再前進到下一步。

中止、重試和忽略

發出這個 sails-lift 後,您或許有點震驚。Sails 沒有安靜愉快地繼續執行,它會立刻將以下信息發送到您的控制檯:

safe/alter/drop?

Excuse my interruption, but it looks like this app

 does not have a project-wide "migrate" setting configured yet.

 (perhaps this is the first time you're lifting it with models?)

 In short, this setting controls whether/how Sails will attempt to automatically

 rebuild the tables/collections/sets/etc. in your database schema.

 You can read more about the "migrate" setting here:

 http://sailsjs.org/#/documentation/concepts/ORM/model-settings.html?q=migrate

 In a production environment (NODE_ENV==="production") Sails always uses

 migrate:"safe" to protect inadvertent deletion of your data.

 However during development, you have a few other options for convenience:

 1. safe  - never auto-migrate my database(s). I will do it myself (by hand) 

 2. alter - auto-migrate, but attempt to keep my existing data (experimental)

 3. drop  - wipe/drop ALL my data and rebuild models every time I lift Sails

 What would you like Sails to do?

現在,您可能注意到的第一件事是,Sails 的錯誤消息是最容易理解的。這不是唯一的方式:Sails 還會生成其他難以閱讀的錯誤消息,尤其在與我們在一些編程語言中獲得的錯誤相比時。

因爲您是向新的 BlogEntry 發出的第一個 sails lift 請求,所以 Sails 會問:我應該如何處理將用來存儲此模型的底層數據庫系統的模式(如果有)?

對於許多開發人員,還會停下來思考以下問題:其他 ORM 系統有一些要求您手動編輯和管理數據庫模式的基本行爲,而 Sails 希望代表您管理這些任務。由於 Sails 無法對 “舊” 模式和 “新” 模式執行任何類型的語義比較,而且在您能進行多大程度的重構纔不會讓 Sails 無法理解語義方面,可能有些限制,所以選項有點少。將這種模式遷移合併到環境中非常不錯,至少在它阻礙您工作或犯錯之前是這樣的。所以最好花點時間想想您的選擇,並始終確保備份了您的代碼和數據。

與此同時,返回到控制檯,Sails 也希望知道它在重構模型時該如何做:

· safe:假設開發人員將管理所有模式重構。

· alter:儘可能地在不丟失任何數據的情況下修改現有模式(這肯定存在失敗的風險)。

· drop:從頭開始創建新模式。

在開發週期的早期階段,每次都重新構建模型是您的最佳方法,因爲您將不斷調整模型類型。不用說,您絕不會爲生產系統選擇此選項。在將應用程序部署到生產中時,您可能需要選擇第一個選項,因爲大部分組織都對任何可能潛在地截斷一個表的操作都很敏感,更別提整個數據庫。但是因爲您仍在實驗並使用 sails-disk 存儲系統(它沒有任何模式),所以可以隨意選擇 “drop” 並保持簡單性。

自動化構建配置的時機

    用不了多久,您就會厭倦在每個 sails lift 上執行 “safe/alter/drop” 查詢。在將應用程序部署到生產中後,這些查詢就會從煩惱演變爲明顯的危險(想想將應用程序部署到生產中的人選擇 “drop” 選項時會發生什麼)。幸運的是,您可以通過編程方式爲 Sails 配置您最喜歡的選項。但在應用程序進一步完善後這樣做可能更有意義。就個人而言,我更喜歡詢問,而不是設置一個可能某一天產生事與願違的結果的自動 “drop”。我不想 Sails 在應用程序重新啓動時丟棄我的生產數據。

選擇 drop 選項後,您將在控制檯中看到熟悉的帆船圖案。Sails 在正常運行,是時候開始將數據注入您的模型了 — 但是該如何操作?

Blueprints API

    Sails 隨帶了預定義的 HTTP 端點來支持基本的 CRUD 操作(GET、POST、PUT 和 DELETE)。每個選項都連接到一個從您模型的名稱生成的 URL 端點。因此,即使只設置了博客 API 的框架,向 http://localhost:1337/BlogEntry 發出 GET 請求也會返回合法的 JSON 響應,如下所示:

圖 1. GET /BlogEntry

wKiom1gcGLSQX2GuAAD_Q8VPE6w646.png

儘管屏幕截圖是空的,但上面顯示的 JSON 數組告訴我 Sails 藍圖正在運行。在這種情況下,藍圖正在運行爲一個 HTTP GET 端點定義的路由。剩餘 CRUD 端點不難確定:POST 到 /BlogEntry,這會使用 POST 正文作爲要插入的數據來創建一個新的 BlogEntry 對象。PUT 將修改一個現有的 BlogEntry,DELETE 會刪除它。(請注意,PUT 和 DELETE 都將切斷提交的 HTTP 請求中的主鍵屬性。)

在典型的 Web 開發場景中,這可能是您開始編寫 curl 命令行腳本的時刻,因爲瀏覽器默認情況下不會執行 PUT 或 DELETE 操作,而是需要一個 HTML 表單來啓動 POST 請求。該操作非常單調和無聊,而且(對 Sails 開發人員而言)完全沒有必要。除了基本的 4 個 HTTP 端點之外,得益於 Sails 開箱即用地設置的藍圖默認設置,您還可以使用一些 “快捷” 路由使用來自瀏覽器的 GET 請求將記錄放入系統中。舉個恰當的例子,如果您在瀏覽器中訪問:

/BlogEntry/create?title=%27Fred%27&body=%27Hello world%27&id=1",

Sails 將使用以下信息作爲響應:

wKiom1gcKujBbg3aAAClPjwNFBw968.png

圖 2. 一個包含一些數據參數的 CREATE毫無疑問,如果您返回並再次發出 GET /BlogEntry 請求,則會顯示一個實例。現在您可能注意到,此設置不太像 REST 風格,因爲它使用 GET 動詞來執行修改數據庫的操作。儘管明顯違背 REST 系統的規則,但在此刻,它是開發週期中值得擁有的一個不錯選項。

而且在將系統部署生產中之前,您應該關閉此實例和所有其他自動生成的路由。首先,擁有兩種將數據插入系統中的可能方式會導致代碼重複(有兩個必須創建的端點,例如,如果您對誰可以將新博客條目放入數據庫中進行限制)。其次,擁有執行同一種操作的多種方法可能會讓將繼承您的系統的開發人員混淆。很好地理解這些演示和早期原型設計,一旦經過了早期階段,它們就沒那麼有用了。

如何處理非唯一主鍵?

   如果實驗 /create 選項,您很快就會發現(與所有合理的數據庫定義相反)主鍵的 id 字段實際上不是唯一的。可以運行同一個請求多次,最終在系統中得到多個BlogEntry 實例,每個實例都擁有一個值爲 1 的 id 字段。這是因爲 sails-disk 適配器不是僅爲開發用途而設計的。sails-disk 適配器實現中缺少通常包含在數據庫中的許多功能:該功能只是其中之一。

藍圖路由

完整的 Blueprints API 文檔 詳細介紹了爲 Sails 配置的所有藍圖路由。除了執行創建/插入的快捷路由(您已在上文看到)之外,還有兩個自動生成的藍圖路由也值得討論:

· PUT /:model/:record 是對記錄執行更新的默認路由。

· GET /:model/:record/update 允許您將要更新的屬性指定爲查詢參數對,比如:GET /BlogEntry/1/update/title=New%20Title

您可以指定 PUT 請求或 GET “快捷” 請求上的參數,但同樣地,應該爲了生產用途而關閉該快捷路由。

GET /:model 有許多參數可作爲查詢參數傳遞給 GET 請求。例如,limit 可用於設置應返回的最大條目數,skip 可用於表明在返回之前跳過了多少個條目。一起使用這兩個參數可爲大型記錄集提供簡單的分頁支持:當用戶在以 50 個記錄劃分的記錄塊中移動時,每移動一次就會執行一次 limit=50, skip 0, 50, 100, 150,依此類推。

GET 藍圖路由也支持 sort,這允許您指定某個要排序的屬性,以及該屬性應按升序 (ASC) 還是降序 (DESC) 進行排序。(一定要確保在屬性名和順序之間放入了一個空格,就像這樣:?sort=lastName$20ASC。)

另一個受支持的參數是 where,它將接受一個 JSON 風格的類似 MongoDB 的謂詞,比如 ?where={"name":{"contains":"theodore"}}(這是一個 Waterline WHERE 條件對象,Waterline 將針對關係數據庫適配器而將它調整爲 SQL。)

如果要搜索的屬性有一個特定的值(無處不在的 SELECT * FROM persons WHERE lastName='Flintstone' 查詢),該路由會直接接受它作爲查詢參數,比如:GET /BlogEntry?title=Greetings%20everybody

考慮到您目前僅編寫了十幾行代碼,這是一組不錯的功能。

結束語

    關於在 Sails.js 中建模,還有許多知識需要學習,但現在,已經有足夠的新領域供您探索。在下一篇教程中,將進一步瞭解模型和它們之間的關係;也就是說,將瞭解許多數據系統中存在的經典的 “1對1”、“1對多” 和 “多對多” 關係。建模關係會使您能夠擴展博客 API 來接受評論等信息。

下載

描述

名字

大小

示例應用程序的源代碼

sailsjs-2-source.zip

134KB

參考資料

學習

· 訪問 Sails.js 主頁 以進一步瞭解 Sails.js 的方方面面。

· Waterline 是 Sails 的默認 ORM。

· 進一步瞭解 Waterline 的內置模型方法。

· 與 Sails.js 捆綁在一起的 sails-disk 適配器是一個僅用於預生產用途的持久性對象存儲。

· 要進一步瞭解 Ted 對於對象關係阻抗失配的想法,請參閱 “計算機科學的維生”(TedNeward.com,2006 年 6 月)。                                                                 回頁首


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章