共用方式為



2016 年 3 月

第 31 卷,第 3 期

本文章是由機器翻譯。

程式設計師雜談 - 如何使用 MEAN: 使用 MongooseJS 進行強固的驗證

Ted Neward |2016 年 3 月

Ted Neward2016 年 2 月專欄中 (msdn.com/magazine/mt632276),我轉換 MongoDB 資料庫。如此一來,不難感謝 Node.js 和 MongoDB 的 JSON 型性質的 JSON 型本質。(生命一定會更容易處理資料的轉換是從 a 蘋果如果時)。MongoDB 有更大的好處在於,它會 「 擴充 」 和 「 向外延展 」 輕鬆,更何況是一般開始。但也有顯著的缺點 ︰ 因為 MongoDB 是 「 無結構描述 」 資料庫 (在於結構描述已經預先定義 — 資料庫會保留集合,集合會保留文件與文件基本上只是 JSON 物件),它位於它自己的解構函式的根源內。

首先,重新叫用 MongoDB 查詢是基本上查詢文件 (尋找呼叫的第一個參數),其中包含所要掃描的相符項目集合的欄位。因此,如果查詢"{'fristName': 'Ted'}"執行針對現有的資料庫中的 「 person 」 集合,不會回來 — 查詢文件中的欄位名稱會是拼字錯誤 (「 fristName 」 而不是 「 firstName 」),因此它會比對集合中的任何文件。(除非沒有打錯字文件中在集合中,當然)。 這是無結構描述資料庫的最大的缺點之一 ︰ 簡單的程式碼或使用者未正確輸入可以建立最生氣的 head 突破性質的意外的錯誤。這是,那就天下太平了取得某些語言支援,不論是由編譯器或解譯器。

第二,請注意,我的上一篇專欄中顯示的程式碼讓人聯想到推崇二十期間計算的 「 用戶端-伺服器 」 時代 」 兩層式 」 應用程式。沒有可直接從使用者 (或在此情況下,從 API 取用者) 會接受輸入、 套用和從資料庫中直接重新傳遞給呼叫端重新取得簡單的資料結構的程式碼層。當然是 「 物件導向 」 該程式碼中沒有意義。時不處理分隔,那就天下太平了如果我們可以取得更強的意義上,「 物件性質 」 的伺服器端的程式碼,使各種屬性的一些驗證可能會集中放在同一個地方,例如。舉一個現成的例子: 它是合法的有空白的名字或姓氏的人員? 他執行任何作業在其狀態? 是否有 「 預設 」 狀態,如果選擇不提供,或是空的狀態可接受?

Node.js 了很多,已 wrestled 這些問題 (它是第一次採用 MongoDB 大規模為基礎的語言生態系統的其中一個),不用多說,它已經提供更高招的解決方案稱為 MongooseJS 的問題。它是位於 「 上方 」 的軟體層的 MongoDB,並提供類似結構描述的語言驗證的驗證層,不僅能夠在伺服器端程式碼中建置的 「 網域物件 」 層。因此,它是排序的 「 其他我 ' 「 平均堆疊中。

MongooseJS: 快速入門

現在,應該很簡單,直接了當也重複取得練習 ︰ 「 哪些 '事' 執行您 npm 這次? 」 簡言之,答案是 npm-1,儲存的 「 安裝 」,但有一些混淆,並完全封裝可能會 (人都傾向於 waffle 「 thing 」 和 「 thingjs 」 之間做為封裝名稱節點)、 [npm 尋找 」 或 「 npm 搜尋 」 將會搜尋比對任何條款依照命令列上的封裝的 npm 登錄。或者,輸入所選的搜尋引擎"Mongoose JS",則會產生 1 的網站 (mongoosejs.com),其中會有正確的 npm 形態,以及一堆有關如何使用 Mongoose 文件。(這樣的書籤在瀏覽器中,確定很方便的做法。)

安裝之後,您可以開始定義 Mongoose 「 結構描述 」 物件,將會定義要儲存在 MongoDB 集合中物件的類型。

Mongoosing 個人

Mongoose 為何基本上是兩個步驟來定義 MongoDB 資料庫 API 最上層的 JavaScript 物件模型會使用一些有趣的術語。首先,我們會定義 「 結構描述,「 從傳統類別為基礎的語言 (C#、 c + +、 Java 或 Visual Basic) 看起來像傳統的類別。此結構描述會有欄位、 定義這些欄位的類型以及將值指派給欄位時,選擇性地包含一些驗證規則的欄位。您也可以加入一些方法,執行個體或靜態、 稍後前往。然後,一旦定義結構描述物件之後,您 「 編譯 」 它模型,這是什麼將用來建構這些物件的執行個體。

這裡需要小心注意 ︰ 這仍是 JavaScript,並更重要的是,這會是 JavaScript 準確地描述 ECMAScript 5,沒有任何 「 類別 」 恕不另行通知的概念為多個版本。因此,即使 Mongoose 方法假您正在定義 「 類別 」,它仍原型架構的語言,您知道與愛 (或 loathe)。這有一部分是此兩步驟程序的原因 — 第一個定義物件將做為類別,第二個"new"運算子周圍的 JavaScript/ECMAScript 5 規則定義會隱含地以 「 建構函式 」 或 「 工廠 」 運作的物件。

因此,轉譯的程式碼中,「 需要 ()"ing 放在頂端的 JavaScript 程式碼的一般 「 1 」 區域變數 Mongoose 程式庫之後,您可以使用,來定義新的結構描述物件 ︰

// Define our Mongoose Schema
var personSchema = mongoose.Schema({
  firstName: String,
  lastName: String,
  status: String
});
var Person = mongoose.model('Person', personSchema);

還有各種項目,您可以運用 personSchema 中的欄位,但首先,我們要保持簡單,這會不只可幫助速度更快,使用程式碼,但是也會反白顯示幾個可有關 Mongoose 一路。同樣地,另外而且請注意,模型定義的兩步驟程序 ︰ 首先,定義在結構描述的結構描述函式/建構函式,並接著將結構描述物件傳遞至模型函式/建構函式,以及此模型的名稱。Mongoose 使用者之間的慣例是傳回的物件模型呼叫將會相同,因為這是什麼所定義的型別會幫助您讓它成為類別的視覺效果。

Mongoosing 作用中

定義模型,現在只有事剩下的執行是重新撰寫將使用模型的各種路由方法。最方便的位置開始 getAllPersons 路由中所示 [圖 1, ,因為它會傳回整個集合資料庫,不含篩選器。

[圖 1 重寫 getAllPersons 路由

var getAllPersons = function(req, res) {
  Person.find(function(err, persons) {
    if (err) {
     debug("getAllPersons--ERROR:",err);
      res.status(500).jsonp(err);
    }
    else {
      debug("getAllPersons:", persons);
      res.status(200).jsonp(persons);
    }
});
};

請注意程式碼基本上類似為何發生之前,會執行查詢,並叫用 (同樣地,與規格 」 錯誤,結果 」 做為其參數) 傳入的回呼函式當查詢完成時。不過,現在 Mongoose 藉由比較有更多的結構,在存取路由透過 Personmodel 物件,這將會產生各種強大的優點,如您會看到在一分鐘的時間。

接下來是 personId 中介軟體,因為它是幾乎所有其餘部分使用的路由中所示 [圖 2

[圖 2 重寫 personld 路由

app.param('personId', function (req, res, next, personId) {
  debug("personId found:",personId);
  if (mongodb.ObjectId.isValid(personId)) {
    Person.findById(personId)
      .then(function(person) {
        debug("Found", person.lastName);
        req.person = person;
        next();
      });
  }
  else {
    res.status(404).jsonp({ message: 'ID ' + personId + ' not found'});
  }
});

同樣地,這是中介軟體函式,因此目標是要尋找 Person 物件集合,並將它儲存成要求物件 (需求)。但請注意,在驗證後傳入 personId 有效 MongoDB ObjectId/OID。同樣地,這次呼叫 findById,傳入 ObjectID/OID 傳入顯示 Person 物件。最有興趣的以下是 Mongoose 支援 「 承諾 」 語法/樣式,將會在未來版本中的 JavaScript 裝置 — findById 從傳回的物件是的您可以叫用"then"並在查詢完成時要執行的回呼中傳遞的承諾物件設定。

然後,這兩種方法可讓您執行的動作與之前執行此查詢,例如排序查詢結果,根據欄位在結果中,以特定順序,或選取的欄位 (以隱藏用戶端不應該收到任何敏感資訊) 的子集。這將會是方法呼叫統治尋找之間以及在流暢的樣式,例如 ︰

Person.find({ })
  .sort({ 'firstName':'asc', 'lastName':'desc' })
  .select('firstName lastName status')
  .then(function(persons) {
    // Do something with the returned persons
  });

在此情況下,這會依名字的遞增順序排序結果、 按照姓氏的遞減順序 (不這麼做可讓沒什麼特別意義) 排序結果,然後去除以外的 「 firstName 」,「 姓氏 」 和 「 狀態 」 欄位。查詢 「 修飾詞 」 的完整清單令人印象深刻,而且包含一些實用的方法,例如限制 (裁切掉結果數目)、 略過 (若要跳過傳回的第 n 個結果),計算 (若要傳回的彙總的計數為傳回的文件),並以此類推。

您取得太閃閃,之前,不過,我會補足其餘的路由方法。

取得更新和刪除有問題的人物件使用中介軟體,那些變得相當直接了當使用儲存位置,並刪除 Mongoose 物件本身提供的方法。中所示 [圖 3, ,只要插入新的個人需要具現化新的人員模型,並將儲存在其上的方法。

[圖 3 具現化新的人員模型

var updatePerson = function(req, res) {
  debug("Updating",req.person,"with",req.body);
  _.merge(req.person, req.body);
  // The req.person is already a Person, so just update()
  req.person.save(function (err, person) {
    if (err)
      res.status(500).jsonp(err);
    else {
      res.status(200).jsonp(person);
    }
  });
};
var insertPerson = function(req, res) {
  var person = new Person(req.body);
  debug("Received", person);
  person.save(function(err, person) {
    if (err)
      res.status(500).jsonp(err);
    else
      res.status(200).jsonp(person);
  });
};
var deletePerson = function(req, res) {
  debug("Removing", req.person.firstName, req.person.lastName);
  req.person.delete(function(err, result) {
    if (err) {
      debug("deletePerson: ERROR:", err);
      res.status(500).jsonp(err);
    }
    else {
      res.status(200).jsonp(req.person);
    }
  });
};

就概念而言,它很像之前的非 Mongoose 版本,但沒有真正重要的是,如果微妙的變更 ︰ 邏輯人現在正在多個當地語系化人員模型 (和 Mongoose 自己的持續性實作)。

Mongoose 驗證

現在,只要 grins,您要將數個新規則新增至應用程式 ︰ firstName 和 lastName 都不可以是空白,而狀態只能是幾個可能的值 (a la 列舉型別) 的其中一個。這是其中 Mongoose 的功能建立伺服器端內的網域物件模型都特別強大,因為傳統 O O 思考持有這些類型的規則,應該封裝在物件型別本身並不是周圍使用程式碼。

1,這便其實有點微不足道。在結構描述物件中,欄位會從簡單的名稱 ︰ 輸入組更複雜的名稱 ︰ 在這些物件描述項,則您可以指定物件描述元組與驗證規則。除了一般數字 (最小和最大值) 的驗證和字串驗證 (最小長度和最大長度) 的許多,,您可以指定一系列的 「 狀態 」 欄位可接受的值並定義每個欄位的預設值時沒有指定,這 (當然) 中所示,整齊地符合新的需求, [圖 4

[圖 4 Mongoose 驗證

// Define our Mongoose Schema
var personSchema = mongoose.Schema({
  firstName: {
    type: String,
    required: true,
    default: "(No name specified)"
  },
  lastName: {
    type: String,
    required: true,
    default: "(No name specified)"
  },
  status: {
    type: String,
    required: true,
    enum: [
      "Reading MSDN",
      "WCFing",
      "RESTing",
      "VBing",
      "C#ing"
    ],
    default: "Reading MSDN"
  },
});
var Person = mongoose.model('Person', personSchema);

沒有其他需要變更程式碼基底中其他地方 — 所有人性質有關這些規則會擷取完全它們應該儲存,在結構描述物件的定義。

如果您嘗試現在插入 JSON,,不會遵守右邊的清單的狀態,例如 ︰

{
  "firstName":"Ted",
  "lastName":"Neward",
  "status":"Javaing"
}

儲存方法呼叫內 insertPerson 路由與傳回的 JSON 主體讀取中所示,將會產生 500 的回應, [圖 5

[圖 5 錯誤結果儲存在驗證失敗

{
  "message":"Person validation failed",
  "name":"ValidationError",
  "errors":{
    "status":{
      "properties":{
        "enumValues":[
          "Reading MSDN",
          "WCFing",
          "RESTing",
          "VBing",
          "C#ing"
        ],
        "type":"enum",
        "message":"`{VALUE}` is not a valid enum value for path `{PATH}`.",
        "path":"status",
        "value":"Javaing"
      },
      "message":"`Javaing` is not a valid enum value for path `status`.",
      "name":"ValidatorError",
      "kind":"enum",
      "path":"status",
      "value":"Javaing"
    }
  }
}

差不多 nails 問題出在哪裡那里。

Mongoose Methoding

當然,物件導向表示結合狀態和行為,這表示這些網域物件將不會真的是物件除非您可以將方法附加至物件執行個體或整體的 「 類別 」。這麼做的其實相當簡單 ︰ 呼叫 Mongoose 方法 (執行個體為基礎的傳統方法的方法),或是類別為基礎的靜態定義的方法,並傳入要做為方法主體中,函數就像這樣 ︰

personSchema.method('speak', function() {
  console.log("Don't bother me, I'm", status);
});

不完全就像在 C# 撰寫的類別,但已經相當接近。請注意,所有的其他變更的結構描述物件,以及這些必須全部之前完成結構描述編譯的模型物件,因此這個呼叫才會出現在 mongoose.model 呼叫之前。

Mongoose 版本控制

順便一提,如果您要快速取得 /persons 來一次,產生的輸出包含一些非預期的項目 ︰

[{"_id":"5681d8bfddb73cd9ff445ec2",
  "__v":0,
  "status":"RESTing",
  "lastName":"Castro",
  "firstName":"Miguel"}]

「 __V 」 欄位中,以 Mongoose 以無訊息方式進入混合 (並保留到資料庫) 是版本控制欄位,辨識 versionKey] 欄位中,為 1,它可幫助 Mongoose 辨識文件; 的變更將它視為偵測同時變更這種來源的程式碼檔案上的版本號碼。一般來說,這是只 Mongoose,此為內部詳細資料,但是它可以是有點令人意外,才能看到它出現有第一次。

不過,會引發更有趣的問題 ︰ 您不想要追蹤已建立的文件或文件中,已變更後起來確實很痛苦填寫欄位,每一次接觸到周圍的程式碼的任何部分,或儲存其中一個這些項目都有系統的罕見。

Mongoose 可以協助處理,讓您 「 攔截 」 文件寫入 MongoDB,不論保存,如所示的呼叫之前,先更新欄位右邊的特定存留週期方法 [圖 6

[圖 6 攔截與 Mongoose 的生命週期方法

// Define our Mongoose Schema
var personSchema = mongoose.Schema({
  created: {
    type: Date,
    default: Date.now
  },
  updated: {
    type: Date,
  },
  // ... as before
});
personSchema.pre('save', function(next) {
  // Make sure updated holds the current date/time
  this.updated = new Date();
  next();
});
var Person = mongoose.model('Person', personSchema);

現在,只要建構一個 Person 物件時,它會預設建立的欄位,以保留目前的日期/時間,而且更新的欄位會設定為目前的日期/時間之前傳送至 MongoDB 進行儲存體。Mongoose 呼叫這些 「 中介軟體 」,因為它們是在定義 express,中介軟體函式的精神,但是別搞錯了,這些是特定並包含降到 1 定義的物件。

現在,每當使用者建立或更新,它必須填入這些對應欄位並依需要更新。此外,應該更複雜 (例如成熟的稽核記錄檔中) 有必要,是相當容易,查看如何這無法新增。而不是建立/更新的欄位,沒有 auditLog 欄位中,為陣列,和 「 儲存 」 的攔截程序會只是為該陣列來描述了每次和/或誰做,視需求而定反覆附加。

總結

這只是另一個相當繁重的部分,而且當然沒有 Mongoose 值得探究的更大。不過,一半的樂趣探索新的技術堆疊和平台,且會極 churlish 我不需要這樣的我的忠實讀者更有趣。若要了解的重點是,此配對 Mongoose + MongoDB 提供強大的組合 ︰ 改寫的 MongoDB 資料庫的本質搭配語言強制執行驗證的基本型別,以及執行階段強制執行驗證,可以提供我們最佳的兩個靜態型別和動態的資料值的型別世界。也不是沒有問題,以及粗略的地方,但整體來說,很難對我來說,假設在 MongoDB 中沒有什麼東西可以讓我做事笨 Mongoose 像任何嚴重程式碼撰寫工作。

我幾乎已經完成的項目,在伺服器端,但是我的空間,因此現在...祝各位寫程式 !


Ted Neward是西雅圖 polytechnology 顧問、 講師及指導。他曾經撰寫超過 100 個文件,是 F # MVP、 INETA 主講人,並具有作者及合著者著作。與他連絡 ted@tedneward.com 如果您想要讓他來自與您的小組,或是閱讀他的部落格 blogs.tedneward.com

感謝以下技術專家對本文的審閱: Shawn Wildermuth