构建完整的微服务





5.00/5 (4投票s)
现在我们已经在 Azure 上运行了一个云原生构建、测试和部署管道,并有一个可用的 Azure Function,我们将把它扩展为一个完整的微服务,包含业务逻辑和数据存储。
在本系列第二部分中,我们使用 Visual Studio Code 的默认模板,用 Node.js 和 TypeScript 创建了一个 Azure Function。我们还创建了一个构建管道来构建和测试代码,并将 TypeScript 编译为 JavaScript。通过发布管道,我们自动将函数部署到了 Azure。
在本文中,我们将为我们的函数添加一个数据库,然后将包含所需基础设施的函数部署到开发、测试、验收和生产(DTAP)环境。到本文结束时,我们将创建一个完整的、带有数据库的微服务,可以部署到 DTAP。
创建 Cosmos DB 账户
让我们从为函数添加数据库开始。我们使用的是 Azure Cosmos DB,并带有 SQL API。我们有充分的理由使用 SQL API,稍后您就会看到。对于 JavaScript 或 TypeScript 开发人员来说,使用 MongoDB API 可能更有意义。这意味着,虽然我们使用的是 Cosmos DB,但我们可以使用任何 MongoDB 库(如 Mongoose)来连接它。然而,对于我们接下来要展示的内容,SQL API 就足够了。
转到 Azure 门户并搜索 Cosmos DB。将其放在与您的函数相同的资源组中,以便于后续清理。选择一个唯一的名称和一个靠近您的区域。除此之外,保留所有默认设置。
创建可能需要几分钟,在我们这里是 14 分钟。在此期间,我们可以专注于我们的函数代码。
使用输出绑定插入数据
要从 Cosmos DB 获取数据和向 Cosmos DB 写入数据,我们使用 Azure Function 绑定。例如,如果我们通过输入参数绑定到 MongoDB 查询,我们将自动获得一个记录。同样,我们可以添加一个输出绑定来返回一个对象,它会自动添加到 Cosmos DB。有不同的绑定,例如 Blob、Table 和 Queue 存储、Service Bus 和 Event Hubs,但我们将使用 Cosmos DB 绑定。
您的函数在您的项目中有一个自己的文件夹,该文件夹包含一个 `function.json` 文件。打开此文件时,您会看到两个绑定:“in”和“out”绑定。这实际上是您的 HTTP 触发器请求和响应。您还会看到 GET 和 POST 方法目前是允许的。
让我们从向数据库插入文档开始。为此,我们将仅使用 POST,因此请从配置中删除 GET 方法。为函数添加一个路由也很好,这样我们以后就可以添加其他函数了。因此,在输入绑定中添加一个值为“person”的 `route` 属性。另外,在配置中添加一个额外的输出绑定。
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"post"
],
"route": "person"
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"name": "personOut",
"databaseName": "mydb",
"collectionName": "person",
"createIfNotExists": true,
"connectionStringSetting": "CosmosDBConnString",
"partitionKey": "/partitionKey",
"direction": "out",
"type": "cosmosDB"
}
],
"scriptFile": "../dist/HttpTrigger1/index.js"
}
Cosmos DB 使用分区键来分区您的数据。该字段是必需的,但您可以将其留空或使用一个常量字符串值。我们使用人的首字母作为分区键。我们需要在这里指定分区键,因为我们指示函数在数据库和集合不存在时创建它们。
另外,转到您的 `local.settings.json` 文件并添加 `CosmosDBConnString` 设置。在 Azure 门户的 Cosmos DB 账户的密钥选项卡中找到您的连接字符串。
修复我们的 TypeScript
我们现在可以更改函数,使其将 Person 对象返回给运行时。对于 Person 的 ID,我们将使用 GUID,所以先运行 `npm install guid-typescript`。然后,在您的 TypeScript 文件顶部添加 `import { Guid } from "guid-typescript";`。
对于 Person,我们定义了一个接口。
interface Person {
id: string,
email: string;
partitionKey: string;
firstName: string;
lastName: string;
}
然后在我们的函数中,我们创建一个 Person 对象并将其添加到上下文绑定中,这样 Function Runtime 就知道应该将此 Person 对象添加到 Cosmos DB。我们使用
context.bindings.[name you entered in your out binding configuration]
最后,我们将 Person 对象返回给调用者,以便他们可以存储生成的 ID。
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
let p: Person = {
id: Guid.create().toString(),
email: req.body.email,
partitionKey: req.body.firstName[0],
firstName: req.body.firstName,
lastName: req.body.lastName,
};
context.bindings.personOut = p;
context.res = {
// status: 200, /* Defaults to 200 */
body: p
};
};
现在,启动 Postman 或 cURL 或任何其他能够向 HTTP 服务发送 POST 请求的工具。启动您的函数并将其 URL(应该是 `https://[:port]/api/person` 这样的格式)复制到您选择的工具中。然后,给它一个类似这样的请求体:
{
"email": "john.smith@contoso.com",
"firstName": "John",
"lastName": "Smith"
}
如果一切顺利,它应该返回一个 200 状态码和一个您发送的 Person 对象的副本。由于我们不验证 John Smith 是否已存在,我们可以继续添加 John Smith,但每次都应该得到一个唯一的 ID。
通过在 Azure 门户中找到 Cosmos DB 账户并转到 Data Explorer 选项卡来确认它确实有效。
使用输入绑定读取数据
现在,创建一个第二个函数来从 Cosmos DB 读取 Person。在 VS Code 中,转到 Azure 选项卡,点击“Create Function”并选择“HTTP trigger”。这会在一个单独的文件夹中创建 HttpTrigger2。
再次打开 `function.json` 文件,但这次删除 POST 方法,并添加一个值为 `person/{partitionKey}/{id}` 的路由。Cosmos DB 输入绑定看起来有点像输出绑定,只是我们需要指定 ID 和分区键。我们在这里使用路由参数。
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get"
],
"route": "person/{partitionKey}/{id}"
},
{
"name": "personIn",
"databaseName": "mydb",
"collectionName": "person",
"connectionStringSetting": "CosmosDBConnString",
"id": "{id}",
"partitionKey": "{partitionKey}",
"direction": "in",
"type": "cosmosDB"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "../dist/HttpTrigger2/index.js"
}
这次,我们可以从 `context.bindings.[binding name]` 中读取 Person。分区键和 ID 从您的 URL 中获取,因此我们可以通过浏览 `https://[your URL]/api/person/[initial]/[ID]` 来获取 Person。TypeScript 代码很简单。
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
context.res = {
// status: 200, /* Defaults to 200 */
body: context.bindings.personIn
};
};
因此,在不编写任何 Cosmos DB 代码的情况下,我们能够向数据库插入文本并从中读取数据。
修复我们的发布
不幸的是,这个更改破坏了我们的发布。如果您推送更改,您会看到一切都被发布并且似乎正常工作,直到您实际向 URL 发出请求。
这是因为 `local.settings.json` 文件不在源代码管理中,但我们将连接字符串添加到了其中。要确认这确实是您的问题,请转到 Azure 门户中的 Function App,然后转到“Configuration”,并添加一个值为您的连接字符串的 `CosmosDBConnString`。应用程序将重新启动,应该就能正常工作了。
我们实际上可以将此解决方案添加到我们的发布流程中。在您的 Azure DevOps 发布管道中,将其添加到“Deploy Azure Function App”任务。
在任务中,找到“App Settings”,然后添加 `-CosmosDBConnString $(CosmosDBConnString)`。
现在,添加 `CosmosDBConnString` 变量并设置值,将作用域设置为“Development”,并通过点击锁将其设置为机密。连接字符串将添加到您的 Function App 中,没有人能够看到该值(他们只能设置新值)。通过创建新的发布来确认其有效性。
部署基础设施
现在一切都正常了。如果您对 TypeScript 进行更改并将其推送到存储库,它将自动构建、测试和部署。但是,如果我们想创建另一个环境,我们将不得不手动创建 Function App 和 Cosmos DB,这既耗时又容易出错。
我们可以使用 Azure Resource Management 模板(更广为人知的名称是 ARM 模板)、PowerShell、PowerShell Core、Bash 或 Shell 来在 Azure 中创建资源。在您的管道中添加一个新任务,然后查找 Azure CLI 任务。将其放在 Azure Functions 任务之前。设置连接,选择脚本类型为 Shell,位置为 Inline script。
我们现在可以在构建中添加 Azure CLI 命令。您可能希望将一些脚本添加到您的存储库中然后使用它们,但为了简单起见,我们使用内联脚本。
让我们从创建一个资源组开始,然后我们可以添加 Function App,它还需要一个存储账户。
az group create --name $(ResourceGroupName) --location $(Location)
az storage account create --resource-group $(ResourceGroupName) --name $(StorageAccountName) --location $(Location) --sku Standard_LRS
az functionapp create --resource-group $(ResourceGroupName) --name $(AppServiceName) --storage-account $(StorageAccountName) --consumption-plan-location $(Location) --functions-version 3 --os-type Linux --runtime node
现在,转到 Variables 并添加 `ResourceGroupName`、`Location` 和 `StorageAccountName`。将 `Location` 设置为“westeurope”或“westus”或您附近的任何区域。
您可以使用资源组的名称作为 `ResourceGroupName`,或者使用一个新的名称,最好以“-dev”结尾,这样您就知道这是您的开发环境。如果您为资源组使用另一个名称,您也必须为 Function App 和存储账户使用新名称。
存储账户名称在整个 Azure 中必须是唯一的,只能包含字母,并且不能超过 24 个字符。这是一个严格的命名策略,但 Azure 就是这样工作的。
确保除 `Location` 之外的所有变量都作用于“Development”。填写完空白后,保存并运行部署,并检查是否没有错误。此部署可能需要几分钟,因为它正在您的 Azure 账户中创建新资源。
部署 Cosmos DB
如果一切顺利,请在您的管道中添加另一个 Azure CLI 任务,并将其放在 Azure Functions 任务之后。在此 CLI 中,我们将创建 Cosmos DB 账户,读取连接字符串,并将其放入 App Service 的配置中。如果我们一直手动添加连接字符串,我们将需要进行一次发布,手动获取并设置连接字符串,最后进行第二次发布。然而,我们希望它一次性完成。
az cosmosdb create --resource-group $(ResourceGroupName) --name $(CosmosDBAccountName) --locations regionName=$(Location) failoverPriority=0 isZoneRedundant=False
CONN_STRING=$(az cosmosdb keys list --name $(CosmosDBAccountName) --resource-group $(ResourceGroupName) --type connection-strings --query "connectionStrings[0].connectionString" -o tsv)
az webapp config appsettings set --resource-group $(ResourceGroupName) --name $(AppServiceName) --settings "CosmosDBConnString=$CONN_STRING"
一旦 Cosmos DB 成功部署,查询连接字符串并将其存储在脚本变量中。最后,将 `CosmosDBConnString` 添加到我们的 Function App 的配置中。通过在脚本中执行此操作,我们可以部署任何我们想要的东西,更改名称,更改环境等等,并且我们总能获得连接字符串。
您现在可以从 Azure Functions 任务和变量中移除连接字符串。
部署 DTAP
现在,转到您的管道概览,在那里您可以看到工件和阶段。将鼠标悬停在“Development”阶段上,然后点击出现的“Clone”按钮。这将创建“Copy of Development”阶段,您可以将其重命名为“Test”。两个阶段之间有一条线,表示“Test”将在“Development”之后部署。点击“Test”阶段上的闪电按钮将触发器更改为手动。这将防止您的代码在推送到存储库后自动进入您的测试环境。如果您愿意,可以设置部署前审批。如果您进行大量推送但又不想在测试人员测试时打扰他们,您可能更喜欢手动方法。
所以,很酷的一点是,您的整个“Development”阶段都被复制了,包括您的变量。但是,您的变量现在被设置为“Test”作用域。这意味着您只需要更改这些变量,就可以部署一个全新的环境!
由于我们在脚本中没有使用任何硬编码的名称,我们甚至不需要查看它们。所以,在您的变量中,输入作用域为“Test”的变量的新名称。只有您的位置保持不变,因为它作用于整个发布。
设置完变量后,保存,创建一个新的发布,触发测试阶段,然后转到 Azure 门户确认一切正常。尝试使用 Postman 触发您的 Azure Function 来插入和读取 Person。
后续步骤
在本文中,我们为 Azure Function 添加了 Cosmos DB 输入和输出绑定。当然,您也可以像往常一样,使用 npm 包连接到任何数据库。通过将基础设施添加到上一篇文章的发布管道中,我们能够快速轻松地启动一个新环境,无需手动干预,也无需追赶 bug。
正如您在本系列中所学到的,只需稍作设置,您就可以自动化您过去手动执行的任务。这使您有更多时间专注于更重要的项目,例如创建新功能来给您的客户带来惊喜,同时根据需要轻松扩展以支持您不断增长的客户群。
恭喜!完成本教程后,您就正式成为云原生开发者了。如果您准备好学习更多,请查看本教程所构建技能的进阶云原生系列。
要了解更多关于在 Azure 上使用 Kubernetes 的信息,请探索Kubernetes Bundle | Microsoft Azure 和Hands-On Kubernetes on Azure | Microsoft Azure。