2018 年 1 月

第 33 卷,第 1 期

数据点 - 创建与 Cosmos DB 交互的 Azure 函数

作者 Julie Lerman

Julie Lerman上一期专栏中,我逐步介绍了如何生成简单的通用 Windows 平台 (UWP) 应用(一款名为 CookieBinge 的游戏),我基本上以此为由探索了如何在设备绑定 Windows 10 应用中使用 Entity Framework Core 2.0 (EF Core)。添加可移植数据库(如 SQLite)的提供程序让此应用变得圆满、可行。

此应用当前将游戏得分存储在设备本地,而用户在设备上玩游戏时是使用 EF Core 2.0 将数据暂留到 SQLite 数据库中。在上一期文章中,我承诺过,此游戏的下一轮迭代会支持用户与 Internet 上的其他游戏玩家共享数据。为了实现这一目标,我将使用 Azure 的两项炫酷功能,即 Azure Cosmos DB 和 Azure Functions。

Azure Cosmos DB 略谈

Azure Cosmos DB 是新一代 Azure DocumentDB。我已在本专栏中介绍过 Azure DocumentDB 这项最初的技术很多次。在 2015 年 6 月刊的“Microsoft Azure DocumentDB 概述”(msdn.com/magazine/mt147238) 及后续两篇文章中,我通过 Node.js 服务器 API,将 DocumentDB 用作 Aurelia Web 应用的后端。

DocumentDB 已经发展成为全局分布式数据库,具有一些非凡功能。除了可以轻松进行全局分布外,它的一致性模型已经过调整,以提供其他更多特性以供选择,而不仅仅只是稳固不变性。在两个极端之间,用户现在还可以选用有限过期、会话或一致前缀。还有其他许多重要的数据存储支持功能,文档数据库现新增了其他许多数据模型,包括通过 MongoDB API 访问的文档、图形数据库、表(键/值对)和列数据存储。Cosmos DB 囊括了所有这些模型。实际上,若有 Azure DocumentDB,它会自动切换为 Azure Cosmos DB,以便现有文档数据库能够受益于 Cosmos DB 的所有新功能。可以从访问 cosmosdb.com 开始,详细了解 Cosmos DB 更多详情。

Azure Functions 略谈

我将使用 Cosmos DB 存储 CookieBinge 游戏得分。不过,我不会使用 DocumentDB API 自行编写所有代码,而是利用 Azure 的另一项相对较新功能,即 Azure Functions。Azure Functions 是 Microsoft 提供的“无服务器计算”产品/服务。我曾一直对这个短语非常怀疑,因为计算仍是在服务器上进行,不仅仅只是我的服务器这样而已。不过,在我最终有机会使用 Azure Functions 后,我现在对此概念钦佩不已。借助 Azure Functions,可以专注于要在应用中执行的实际逻辑,同时产品本身还能处理好贯穿各领域的各个关切方面,如部署、支持 API 和连接到其他功能(如数据存储或发送电子邮件)。只有在自己实际操作后,我才完全理解此概念。所以,我希望大家能够与我一起准备为 CookieBinge 应用启用此功能,这样大家也会像我一样恍然大悟。

生成首个 Azure 函数的准备工作

生成 Azure 函数有三种方法。第一种是使用 Visual Studio 2017 中的工具。第二种是直接在 Azure 门户中执行操作。第三种是将 Visual Studio Code 与 Azure 命令行接口 (CLI) 结合使用。我之所以决定从门户入手开始学习如何操作是因为,这样我可以了解需要执行的所有步骤。另一个好处是,不使用 Visual Studio 2017 工具,我就不得不更努力地思考所有活动部件。我觉得,这样我可以有更好的理解。当然,Visual Studio 2017 中也有许多可执行此操作的极好资源。不过,我喜欢使用门户的另一个原因在于,它是支持跨平台的 Web 门户。请注意,虽然可以将函数代码和资产从源代码管理部署到 Azure 中,但直接在门户中生成的所有内容都必须先下载到计算机中(简单任务),再从计算机推送到存储库中。

若要继续与我一起执行操作,但尚无 Azure 订阅,我很高兴告诉大家,可以获取免费订阅,而不仅仅只是短期试用订阅。一些 Azure 产品免费一年,还有几十款产品则是永久免费。请转到 azure.com/free 进行设置。我使用的帐户是在订阅 Visual Studio 期间获取的,它提供每月信用额度补偿,可用于试用 Azure。

创建函数前,我需要定义我的目标。我希望应用能够:

  • 在 Web 上存储用户游戏得分,不仅将一些用户信息和日期与得分一起暂留,还暂留用户玩游戏所用的设备类型。
  • 允许用户跨所有玩游戏使用的设备检索最高得分。
  • 允许用户跨所有用户检索最高得分。

我不会将本课程拖入创建和验证帐户等问题的泥潭,尽管在实际操作过程中需要解决这些问题。我的目标是要介绍 Azure Functions,并最终介绍如何从 UWP 应用与数据库进行交互。

在 Azure 门户中创建 Azure 函数

Azure Functions 是一项以函数应用为单位进行分组的服务,可方便用户跨函数集定义和共享设置。那么,我将从新建函数应用入手。在 Azure 门户中,单击“新建”,再筛选“函数应用”,以轻松找到此选项。依次单击结果列表中的“函数应用”和“创建”,将会看到填写特定元数据(如应用名称)的提示。我将函数应用命名为“cookiebinge.azurewebsites.net”。由于这只是一个简单演示,我将接受“创建”页上的其余默认设置。为了方便以后能够轻松访问新建的函数应用,请选中“固定到仪表板”选项,再单击“创建”按钮。大约只需 30 秒时间,即可完成部署新建的函数应用。

现在,可以向函数应用添加一些函数了。我将生成函数,以支持上面提及的目标列表。Azure Functions 服务预定义了一组可以响应的事件(相当丰富),包括 HTTP 请求、Cosmos DB 数据库更改或 blob /队列事件。由于我需要从 UWP 应用调用这些函数,因此希望函数能够响应 HTTP 请求。门户提供各种语言的大量模板,包括 Bash、Batch、C#、F#、JavaScript、PHP、PowerShell、Python 和 TypeScript。我将使用 C#。

若要创建函数应用中的首个函数,请单击“函数”标头旁边的加号。将会看到创建预定义函数的按钮。不过,如果向下滚动到这些按钮下方,将会发现创建自定义函数的链接。选择此选项,将会看到填充了模板选项的可滚动网格,如图 1 所示。“HTTP 触发器 - C#”应位于此列表的顶部,这就是应选择的选项。

新建自定义 Azure 函数的模板列表概览
图 1:新建自定义 Azure 函数的模板列表概览

命名函数,再单击“创建”按钮。我将函数命名为“StoreScores”。

门户将创建包含特定默认代码的函数,这样函数的结构一目了然。此函数在 run.csx 文件中生成(见图 2)。还可以在支持文件中使用附加逻辑,但这是更加高级化的操作,对于这次初探函数来说是富余的。

新 HTTPTrigger 的默认函数逻辑
图 2:新 HTTPTrigger 的默认函数逻辑

在上面的示例中,只有一个方法 Run,这是 Azure 在响应对此函数发出的 HTTP 请求时调用的方法。它包含一个用于捕获请求的参数,以及另一个用于将信息中继到日志的参数。

在此示例中,可以看到函数查找的是表示姓名的传入数据,并且非常灵活,能够在查询参数和请求正文中搜索此类数据。如果找不到姓名,此函数会返回包含友好错误消息的 Http­ResponseMessage;否则,它会在响应中返回“[姓名],你好”。

将函数自定义为与 Cosmos DB 交互

此函数的目标是将传入数据存储到 Cosmos DB 数据库中。下面我们将开始发挥它的魔力。无需创建连接和命令以及其他任何代码,即可执行此任务。Azure Functions 可以与其他许多 Azure 产品轻松集成,Cosmos DB 就是其中之一。

在“函数”列表中,应该会看到新建的函数及其下方的三项。其中一项就是“集成”。选择此项,将会看到部分表单,如图 3 所示。请注意,其中指明了触发器是 HTTP 请求,输出通过 HTTP 返回内容。因为我要返回成功或失败消息,所以确实要保留此 HTTP 输出。不过,我还想添加一个将 Cosmos DB 集合用作目标的输出。

定义函数集成点
图 3:定义函数集成点

为此,请单击“新建输出”,将会看到图标列表。向下滚动到并选择“Azure Cosmos DB”图标,再继续向下滚动页面,将会看到“选择”按钮。大家都知道该怎么做了。(就是单击此按钮!)

用于设置此集成的屏幕预填充有默认设置。“文档参数名称”表示在 run.csx 中使用的参数。我将保留默认名称“outputDocument”不变。接下来需要设置 Cosmos DB 数据库及其中集合的名称,以及与数据库所在 Cosmos DB 帐户的连接。将会看到用于自动创建数据库的复选框。由于我已经创建了几个 Cosmos DB 帐户,因此我会使用其中一个帐户。不过,我会让此函数在所用帐户中新建“CookieBinge”数据库及其中的“Binges”集合。图 4 展示了在保存输出定义前我是如何填写此表单的。因为我选中了用于创建数据库和集合的复选框,所以它们会自动进行创建,而不是在我保存此输出时才创建。如果此函数首次尝试将数据存储到数据库中,但发现数据库并不存在,则会快速创建数据库。

为函数定义 Cosmos DB 输出
图 4:为函数定义 Cosmos DB 输出

自定义 Run.csx

现在,是时候重新定义函数代码了。新版函数需要传入与此 BingeRequest 类一致的 JSON 对象,我将此类添加到了 run.csx 文件中的 Run 方法下:

public class BingeRequest{
  public string userId {get;set;}
  public string userName {get;set;}
  public string deviceName {get;set;}
  public DateTime dateTime {get;set;}
  public int score{get;set;}
  public bool worthit {get;set;}
}

不过,这与要存储的数据的结构并不相同,因为我要再捕获一个属性,即将数据记录到数据库中的日期和时间。为此,我将使用第二个类 BingeDocument(它继承自 BingeRequest,因此继承了它的所有属性),并再添加一个“logged”属性。构造函数需要使用已填充的 BingeRequest,在设置“logged”值后,它会将 BingeRequest 值传输到自己的属性中:

public class BingeDocument:BingeRequest
  {     
    public BingeDocument(BingeRequest binge){
    logged=System.DateTime.Now;
    userId=binge.userId;
    userName=binge.userName;
    deviceName=binge.deviceName;
    dateTime=binge.dateTime;
    score=binge.score;
    }
    public DateTime logged{get;set;}
  }

在这些类型到位后,Run 方法就可以利用它们了。图 5 展示了修改后的 run.csx 列表,包括上述 BingeRequest 和 BingeDocument 类的占位符。

接下来,分析一下新的 Run 方法。它的签名需要使用请求和 TraceWriter,就像原始签名一样,但现在还包含异步输出参数 outputDocument。输出参数结果就是推送到我定义的 Cosmos DB 输出中的内容。请注意,它的名称与图 4**** 中输出配置的输出参数名称一致。使用 TraceWriter,我可以将消息输出到“代码”窗口下方的“日志”窗口中。我最终会把这些去掉,但与以往一样,是在不使用支持调试的 IDE 的情况下。不过,别误会。“代码”窗口非常适用于分析所使用的语言。保存时,所有编译器错误也会非常详细地输出到“调试”窗口中。它还会在用户键入左括号时插入右括号,诸如此类。令人印象深刻的编辑器功能其实有许多。例如,右键单击编辑器窗口可以看到一长串可用的编辑器功能。

图 5:捕获 Binge 并将它存储到输出 Cosmos DB 中的新 run.csx 文件

using System.Net;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req,
  TraceWriter log,    IAsyncCollector<object> outputDocument)
{
  BingeRequest bingeData =  await req.Content.ReadAsAsync<BingeRequest>();
  log.Verbose("Incoming userId:" + bingeData.userId);
  var doc=new BingeDocument(bingeData,log);
  log.Verbose("Outgoing userId:" + doc.userId);
  await outputDocument.AddAsync(doc);
  if (doc.userId !=" " ){
    return req.CreateResponse(HttpStatusCode.OK,$"{doc.userId} was created" );
  }
  else {
    return req.CreateResponse(HttpStatusCode.BadRequest,
      $"The request was incorrectly formatted." );
  }
}
public class BingeRequest{ . . . }
public class BingeDocument { . . . }

Run 中的第一行代码异步读取请求,并将结果转换为 BingeRequest 对象。

接下来,我会实例化新 BingeDocument,同时传入刚刚通过请求创建的对象,这会生成完全填充的 BingeDocument 对象,并填充 logged 属性。

然后,我会使用 TraceWriter 在日志内显示请求中的一些数据。这样在调试时,我就能判断 BingeDocument 是否确实获取了请求对象中的数据。

最后,我会将刚刚创建的 BingeDocument 异步添加到异步 outputDocument 中。

outputDocument 对象的结果就是此函数发送到 Cosmos DB 的内容。它会在 DocumentDB 中存储为 JSON,此函数会在后台再次转换它。由于我是使用集成设置将一切都关联在一起,因此无需编写任何代码,即可实现这一目标。

最后,我会通过 HttpResponse 返回消息,同时中继此函数的成功或失败消息。

编译和测试函数

函数在保存时进行编译。我将从代码中随机删除一些重要内容,这样就可以了解编译器的实际运行机制,结果显示在“代码”窗口下方的“日志”窗口中。图 6 展示了编译器输出,并突出显示了我从第 13 行中删除左括号导致的混乱状况。即使不使用调试程序,我也可以查看这些错误,它们有助于我调试要编写的代码,而无需依靠 IntelliSense 或 IDE 中的其他编码工具。同时,我也发现我是多么依赖这些工具呀!

显示编译器信息(包括错误)的“日志”窗口
图 6:显示编译器信息(包括错误)的“日志”窗口

在修复代码且编译器未发现错误后,日志会先显示“正在重新加载”消息,再显示“编译成功”。

现在,是时候测试函数了。可以在编写函数代码的同一窗口中进行测试。代码编辑器的右侧有两个选项卡式窗格。一个窗格显示与此函数相关的文件列表。默认情况下,只包含两个文件,一个是我当前正在探究的 run.csx 文件,另一个是包含 UI 中定义的所有设置的 function.json 文件。  另一个窗格用于运行测试。这个内置的测试 UI 就像是迷你 Postman 应用或 Fiddler 应用,可以更轻松地创建 HTTP 请求,因为它已注意到此函数是要测试的。只需插入表示传入请求的 JSON 对象即可。测试 UI 默认发送 HTTP Post,所以无需为此测试更改它。在“请求正文”文本框中,输入以下 JSON。虽然架构很重要,但可以根据需要使用任意值:

{
  "userId": "54321",
  "userName": "Julie",
  "deviceName" : "XBox",
  "dateTime": "2017-10-25 15:26:00",
  "score" : "5",
  "worthit" : "true",
  "logged": ""
}

接下来,单击“测试”窗格上的“运行”按钮。此时,测试会调用函数,同时传入请求正文,然后在“输出”窗口中显示所有 HTTP 结果。在图 7 中,可以看到内容为“已创建 54321”的输出,以及“日志”窗口中显示的函数日志输出。

对函数运行测试后的“测试”窗格
图 7:对函数运行测试后的“测试”窗格

查看 Cosmos DB 数据库中的新数据

此时,无法看到首次成功测试的结果,即创建了 CookieBinge Cosmos DB 数据库,并其中创建了存储此文档的 Binge 集合。在结束本期的多篇连载专栏前,我们将来看一看。

为此,可以先在创建此数据库的门户中打开 Cosmos DB 帐户。我的帐户是 datapointscosmosdb。所以,我将转到“所有资源”,并在筛选器中键入数据点来查找它。打开帐户后,我可以看到其中的所有集合和数据库,尽管只有 CookieBinge 数据库中的 Binges 集合,如图 8 所示。这就是此函数刚刚创建的内容。

datapointscosmosdb 帐户中列出的 Binges 集合
图 8:datapointscosmosdb 帐户中列出的 Binges 集合

单击“Binges”,打开此集合的数据资源管理器。由于我已经运行了测试两次,因此在图 9 中可以看到集合中存储有两个文档。文档中的前七个属性是我定义的属性。其余是 Cosmos DB 和相关 API 用来执行索引、搜索、分区等任务的元数据。

在门户中查看 Cosmos DB 中存储的文档
图 9:在门户中查看 Cosmos DB 中存储的文档

预先准备

如果回顾一下图 7,就会发现“代码”窗口上方有“获取函数 URL”链接。这就是我将在 CookieBinge 应用中用来把数据发送到云的 URL。

至此,大家已了解如何创建函数,并将它与 Cosmos DB 相关联。我的下一期专栏文章将会介绍如何再生成两个函数,用于检索不同的数据视图。最后一期专栏文章将会介绍如何从 CookieBinge 应用调用函数,并显示结果。


Julie Lerman**** 住在佛蒙特州的丘陵地区,担任 Microsoft 区域主管、Microsoft MVP、软件团队导师和顾问。可以在全球的用户组和会议中看到她对数据访问和其他主题的介绍。她的博客地址是 thedatafarm.com/blog。她是“Entity Framework 编程”及其 Code First 和 DbContext 版本(全都出版自 O’Reilly Media)的作者。通过 Twitter 关注她:@julielerman 并在 juliel.me/PS-Videos 上观看其 Pluralsight 课程。**

衷心感谢以下 Microsoft 技术专家对本文的审阅:Jeff Hollan


在 MSDN 杂志论坛讨论这篇文章