2016 年 7 月

第 31 卷,第 7 期

此文章由机器翻译。

数据点 - 新的 Azure DocumentDB Node.js SDK

通过 Julie Lerman

Julie Lerman过去几年我曾经开发过在前端来存储其数据在 Node.js 和 Azure DocumentDB 中编写的服务器端 API 使用 Aurelia 示例应用程序。除了为服务器端 API 使用 Node.js,我的应用程序还会利用 Azure DocumentDB Node.js SDK。而不是描述了完整的应用程序,我将指导您访问以前的文章从 11 月份 (msdn.com/magazine/mt620011) 和 2015 年 12 月 (msdn.com/magazine/mt595750) 当我写了有关此应用程序。甚至可以下载原始应用程序,并将它与新源反映在本文中所述的更改进行比较。因为我经常调整应用程序,您始终可以看一看在 GitHub 存储库和 bit.ly/25crZAG

由于对 Aurelia,我使用的 Node.js 包的许多、 对 DocumentDB 功能和更改甚至在过去个月的上述 SDK,它是时候执行包的更新,而不只是很多,还必须更新我的代码来充分利用在整个较新的功能。将不讨论对 Aurelia 更新相反,我将在我的重点在 DocumentDB 中的更改和上修改我 Node.js API 代码能够受益于这些更改。

步骤 1: 承诺实现异步调用

我所做的第一件事就是在我的 Node.js 项目上运行"npm 更新"。更新得好,但以后运行该应用程序是鲜有成功。我快速遇到错误,告诉我的回调是我使用了会成为问题。某个位置中的依赖关系的深度,API 现在 JavaScript 承诺使优先于回调。(类似于在.NET 中 async/await) 的承诺的范例已经推出一段时间,但我好像执行时创建此示例使用回调的熟悉路径。现在,是时候更深入地介绍我鞋跟,按住呼吸,采用承诺替换所有 Node.js API 中的回调。遗憾的是,这不是只需替换术语中,但它需要更改代码的实际结构。在我的 API 的层,我直接向 DocumentDB Node.js SDK 讨论 DocDBUtils 文件中都使用回调。和我讨论了对 (在 DocDBUtils) 实用程序和 SDK 的 DocDBDao 类中使用的回调。这意味着与实用程序进行通信时我必须回调的分层的系统。最后,ninja.js 使类调入 DocDBDao 类与触发器数据检索或更新。这些方法使用回调,并依赖较低的文件的回调。因此,我需要向上实现从底部 (DocDBUtils) 的承诺。

有大量的 JavaScript Api,可帮助完成承诺的实现。其中一个称为 Q 和 DocumentDB 团队为其 Node.js SDK,它使用 Q,并因此,使得使用承诺,编写针对 DocumentDB 容易得多的任务时创建一个包装。此包装,documentdb q 承诺,是在 GitHub 上 bit.ly/1pWYHCE

因此,我第一步是安装 Q 使用节点程序包管理器 (npm):

npm install documentdb-q-promises

然后,在所有已使用基 SDK (前面提到的类,以及一个称为的 api.js) 的节点类中我需要修改了最初让我知道要使用从初始 SDK DocumentClient 类的类的"要求"语句︰

var DocumentDBClient = require('documentdb').DocumentClient;

若要从新的 API 指向 DocumentClientWrapper 类︰

var DocumentDBClient = require('documentdb-q-promises').DocumentClientWrapper;

DocDbUtils 类直接,需要对 Q 库的其他引用,因此它的 DocumentDBClient 定义为︰

var DocumentClient = require('documentdb-q-promises').DocumentClientWrapper
  , Q = require("q");

接下来,我需要重构回调代码,以使用约定。我努力通过这一段时间之前我模式下。然后,使用 DocDBUtils 类中某个正在运行的功能,我就能够更轻松地修复调入此类的类中的函数。但我是怎样到达该点之前,这的确是休止的艰辛︰ 更改代码、 调试、 读取错误、 某些更 scratch 我的头,Google 和再次更改代码。因此我的朋友保留我从太多损害我的头时,还有 griping 在 Twitter 上的位。这不是这么多因为它是非常困难,但只是因为 — 而不考虑我的编程体验,我仍在 JavaScript 中门外汉的事情。

作为示例,首先,我将使用第一个函数,API 运行时命中︰ DocDbDao.js 中的 init 方法。此方法可确保 API 的 rest 的 DocumentDB 帐户能够识别、 就会连接到使用身份验证密钥和知晓该数据库的名称,如中所示 图 1

图 1 原始 getOrCreateDatabase 方法使用回调

getOrCreateDatabase: function (client, databaseId, callback) {
  var querySpec = { *query for database name defined here* };
  client.queryDatabases(querySpec).toArray(function (err, results) {
    if (err) {
        callback(err);
    } else {
      if (results.length === 0) {
          var databaseSpec = {
            id: databaseId
          };
          client.createDatabase(databaseSpec, function (err, created) {
            callback(null, created);
          });
      } else {
        callback(null, results[0]);
      }
    }
  });
},

getOrCreateDatabase 称为从 docDbDao 类中的 init 函数。名为客户端的参数是从原始 SDK DocumentDBClient 一个实例。  第三个参数,名为回调,是指调用函数 — 在本例中为 init。GetOrCreateDatabase 方法 querySpec 变量中定义的查询,然后调用 client.queryDatabase 与查询。如果 queryDatabase 返回一个错误,getOrCreateDatabase 通过回调传递到调用函数返回该错误。否则,它会检查结果。如果 results.length 为 0,它将创建一个新的数据库,然后将传递回调用函数返回 createDatabase 的信息。如果 results.length 不是 0,则返回在回调中返回结果数组中的第一项。

现在让我们了解一下此中所示的相同函数 图 2, 、 重新编写,以便使用 (请记住,这些是像 Microsoft.NET Framework 中异步/等待) 的承诺,利用承诺提供的实现来解答。

图 2 getOrCreateDatabase 使用承诺

getOrCreateDatabase: function (client, databaseId) {
  var querySpec = { *query for database name defined here* };
    return client.queryDatabases(querySpec).toArrayAsync().then(function (results) {
      if (results.length === 0) {
          var databaseSpec = {
              id: databaseId
        };
        client.createDatabaseAsync(databaseSpec)
          .then(function (databaseResponse) {
            db = databaseResponse.resource;
            return client.createCollectionAsync(db._self, collectionDefinition);
          })
      }
      return results.feed[0];
  },
      function (err) { return err; }
  );
}

要注意的第一个问题是该函数的参数列表中有不执行回调。后定义查询,该函数调用 queryDatabases,但不是与之前一样。这一次,我使用 queryDatabases 包装由新的 SDK。而不是调用 queryDatabases toArray,我使用 toArrayAsync 方法,这是其中之一大量 documentdb q 承诺 SDK 提供的异步方法。toArrayAsync 返回由 Q 库定义的约定类型的实例承诺有一个"then"方法 (类似于您可能熟悉.NET Framework 中的 await),允许您定义 queryDatabases.toArrayAsync 调用完成时要执行的函数。逻辑的第一位指示要执行的调用是否成功。就像以前一样,我检查是否的长度为 0,该值指示数据库尚不存在。如果出现这种情况,然后,创建一个新数据库,但这次使用 createDatabaseAsync 方法,其中,像其他异步方法,返回承诺对象。如果成功创建数据库,然后处理数据库响应。我省去了一些额外的逻辑周围创建数据库,但您可以看到它,如果您下载示例代码。

该方法的下一部分指定应发生如果查询未找到一个数据库,它就是在结果中返回的第一项。ToArrayAsync 结果包含源的属性所包装的结果,这就是原因语法看作 results.feed[0]。

最后,如果 queryDatabases 调用将失败,该函数将返回错误。

既然您已通过这,让我们看一下该模式试︰

CallToAsyncFuction().then(function to execute when complete){
                          success logic
                          },
                          function(err) {failure logic}
                          );

可以调用异步方法之一,并使用其 then 方法定义一个无名称的函数调用完成时要执行。在函数的逻辑中,您首先指定代码执行时该方法已成功完成 (有选择地返回某些结果),然后该方法将失败 (也具有返回结果的选项) 时要执行的代码。

我实现了在修改后的 API,替换所有的三种模型中的回调构造此模式。

但是,我不能只是现在调试并希望出现此起作用,因为我有承诺开头 ninjas 类,该类还不知道有关新的 documentdb q 承诺 SDK 瀑布式。您可以尝试以替换原始示例中的这些其他回调自己,或请参阅下载中完全更新后的解决方案。

现在,使用与 DocumentDB 我 Node.js 交互建议使用的技术,让我们看一下在 DocumentDB 中的其他一些功能,以及我是如何实现这些 API 中,我将讨论。

参数化的查询

在我的示例中的第一个迭代,我做了我将永远不会执行我.NET 应用程序中的 ninja.js 类中的某些内容 — 我黑客攻击以及字符串串联的查询字符串。至少我已使用 ES6 启用字符串插值来执行该串联时,但我仍然感到有点羞耻,有没有任何借口。除非可能是两个。第一种是我已提供的示例中学习和尚未使用我的头脑。(未进行此甚至计数)? 第二个是问题的安全没有时刻极其重要,因为执行在 Azure DocumentDB SQL 攻击不是问题的太大由于方式查询而导致在数据库中工作。即使文档中指出,虽然始终有机会寻找一种方法,以利用注入 evildoer DocumentDB 不是真正遭受注入式攻击,最常见的类型。尽管如此,它始终是最好额外谨慎的安全性,并且参数化的查询已进行数据访问是建议的做法很长时间。

在早期版本中的示例中,filter 函数定义一个名为 querySpec 与名为查询的属性类型。查询属性值是 SQL 中,用来从 DocumentDB 检索一组的 ninjas:

var querySpec = {
  query:
  'SELECT ninja.id, ninja.Name,ninja.ServedInOniwaban,ninja.DateOfBirth
  FROM ninja'
}

此筛选器函数将读取在 URL 中包含的筛选器值。例如,当用户搜索名称中包含"San"每个专家,URL 是 localhost:9000/api/ninjas? q = San。原始函数通过只需串联 request.query.q,与该谓词中找到的筛选器值构造查询谓词︰

q = ' WHERE CONTAINS(ninja.Name,"' + request.query.q + '")';

我则附加到已存储在 querySpec.query 的基查询谓词。

即使注入式攻击几乎与轻松使用筛选器值不是,我已经使用 DocumentDB 将包含一个参数替换该位的逻辑。而不是连接输入最终用户 (San) 的筛选器值,我将使用在谓词中调用 @namepart 参数占位符。然后我将一个新属性添加到 querySpec 称为参数,并,使用 JSON 格式,定义使用 DocumentDB 也在寻找的 name 和 value 属性。然后,我可以指定参数名称和 url 中传递的查询值︰

querySpec.query += " WHERE CONTAINS(ninja.Name, @namepart)";
  querySpec.parameters = [{
    name: '@namepart',
    value: request.query.q
}]

DocumentDB 将执行这作为参数化查询以便任何 evil SQL 将无法进行会影响我的数据。

再见本身的链接并不让希望之门点击您的方式扩展

好了,这就是有点残酷,但很多少人认为使用每种类型的对象,从 Selflink 的需要有关是否数据库、 集合、 文档或 DocumentDB 中的其他对象。SelfLink 值是如何标识的对象本身在 DocumentDB 中。您将不得不带有已知标识符查询 DocumentDB — 数据库或集合的名称或文档的标识值,以便使其 selfLink,因此您可以执行其他操作。Selflink 仍然存在,但您不再需要它们与对象交互。如果您知道要建立指向对象的详细信息,您可以使用,而不是 selfLink。我将演示,稍后在与下一步功能结合使用我已经利用了在我修改后的示例︰ Upserts。

替换 Upsert 替换

我是希望在我需要我先检索要更新的项以便为数据库中的 API 中删除的笨拙的 update 函数︰

  1. 确保它已存在
  2. 有权访问其 selfLink
  3. 在传递给该更新方法的项具有有限的架构的情况下有权访问完整的文档

然后,我不得不更新从文档中的 update 方法从客户端应用程序传递的项的值从数据库中检索的字段。最后,我需要告诉 DocumentDB 数据库中现有的文档将替换为此修改后的文档。您可以访问早期版本的文章或示例,以看看 updateItem 函数中 docDbDao 如果您感兴趣,这是什么所有如下所示。

幸运的是,在 10 月 Microsoft 宣布添加到 DocumentDB 中,从而使其能够找出给定的文档需要插入或更新的原子 upsert 功能。请参阅相关的博客帖子,网址 bit.ly/1G5wtpY 更详细的说明。

Upsert 使我可以执行简单更新使用我在手头准备好的文档。Documentdb q 承诺 SDK 提供了替换的异步版本。下面是我修订的更新函数︰

updateItem: function (item) {
  var self = this;
  var docLink = "dbs/Ninjas/colls/Ninjas";
  item.DateModified = Date.now();
  return self.client.upsertDocumentAsync(docLink, item).then(function (replaced) {
    return replaced;
  },
    function (err) {
    return err;
    }
  );
},

请注意我要构建的文档链接值。这是我提到过新功能,能够帮助我避免从数据库实际的 SelfLink 我想要更新的文档。我只指定的数据库名为 Ninjas 和该集合也恰好名为 Ninjas。我将传递到 upsertDocumentAsync 命令中,来自于客户端的项以及文档链接值,然后传递回 (替换) 时执行此命令返回的布尔值。此外请注意,与异步命令,我已修改此逻辑才能利用 Async 方法返回的承诺。因为我要在异步方法然后撰写,可以判断。

很多其他已添加到 DocumentDB

虽然我的小示例中利用只有几个新功能后,很多其他已添加到 DocumentDB 因为我编写了我在专栏前半。多个 SQL 命令时在那里,包括用于分页的顶部和 ORDER BY 排序。ORDER BY 依赖索引的集合,这是有道理,因为这在这里,是有关大型数据,您需要优化数据库以满足您的特定需求。还有可能要修改现有的集合,而不必重新忍受令人遗憾的以前选择的索引策略。如果您正在使用.NET 客户端 API for DocumentDB,LINQ 提供程序已成为更丰富,您可以在阅读有关 bit.ly/1Od3ia2

DocumentDB 是保留大量的数据,,Microsoft 正致力于使访问更加高效且经济高效。为此,该公司引入了分区键,一个核心功能的 DocumentDB,允许你向分区的集合来适应"大型高工作率或需要高吞吐量、 低滞后时间访问数据的应用程序的数据量"。 您可以了解有关分区键在 bit.ly/1TpOxFj

Microsoft 还涉及定价与创建其他集合的集合的数量与相关费用因为保留用户的问题。新的计划基于数据量和吞吐量,因此您可以使用多个集合并不担心非常那样了解对于不具有大量活动的集合的成本过高。但每个集合仍具有最小的吞吐量。该团队坚持不懈地寻找改进资源使用状况,以便在将来,则会降低这些最小值的方法。有关新定价计划的详细信息,请访问 bit.ly/1jgnbn9

DocumentDB 工具也已经更改,并将继续为此,在将来。有更多方式来浏览在 Azure 管理门户中,您 DocumentDB 和数据迁移工具获得更多的功能。我已特别高兴地看到一个更改是 Visual Studio 的云资源管理器扩展插件现在支持连接到并利用 DocumentDB 数据。您甚至可以编辑和保存对通过云资源管理器,原始数据的更改,但在此期间,查询不是一个选项。

若要及时处理不断增长的功能集,关注 Azure 博客上的 DocumentDB 标记篇博客文章 (bit.ly/1Y5T1SB),然后按照 @documentdb Twitter 帐户是否有更新。


 

Julie Lerman是 Microsoft MVP、.NET 导师和顾问,住在佛蒙特州的山区。您可以在全球的用户组和会议中看到她对数据访问和其他 .NET 主题的演示。她的博客地址是 thedatafarm.com/blog。她是“Entity Framework 编程”及其 Code First 和 DbContext 版本(全都出版自 O’Reilly Media)的作者。请关注她的 Twitter: @julielerman 并查看其 Pluralsight 课程,网址 juliel.me /ps-videos

感谢以下的微软技术专家对本文的审阅: Andrew Liu