将 API 从 Express 迁移到无服务器功能时,如何构建代码?
有很多文章介绍了如何将无服务器函数用于各种用途。其中很多文章介绍了如何入门,而且非常有用。但是,当你想像整理 Node.js Express API 那样更有效地组织它们时,该怎么做呢?
关于这个话题有很多值得讨论的地方,但在本文中,我想特别关注一种组织代码的方法。欢迎在评论区留言,告诉我你还对哪些方面感兴趣,我会考虑在以后陆续更新。
以下是我推荐的一些入门资源:
- 您的第一个带有 VS Code 函数扩展的 Azure 函数
- Azure Functions 概述
- 开发者指南
- GitHub Repo 中的示例代码
- 适用于 Cosmos DB 的 JavaScript SDK
为什么要结构化你的代码?
你可以将所有函数逻辑放在一个文件中。但你想这样做吗?共享逻辑怎么办?测试?调试?可读性?这时,模式或结构就能派上用场了。有很多方法可以做到这一点。除了上面提到的方法之外,一致性是我主要关注的附加方面。
这是一个函数应用程序的非常标准的表示:
FunctionApp
| - host.json
| - myfirstfunction
| | - function.json
| | - index.js
| | - ...
| - mysecondfunction
| | - function.json
| | - index.js
| | - ...
| - sharedCode
这是我的结构,仅用于英雄 API。
你的入口点
函数的入口点位于与index.js
函数同名的文件夹中的文件中。
这个函数本身非常直观。调用此函数时,会将上下文传递给它。上下文可以访问请求和响应对象,这非常方便。然后我调用异步操作来获取数据并设置响应。
// heroes-get/index.js
const { getHeroes } = require('../shared/hero.service');
module.exports = async function(context) {
context.log(
'getHeroes: JavaScript HTTP trigger function processed a request.'
);
await getHeroes(context);
};
另一种方法是通过
context.res
和/或context.req
进入服务。如何选择由您决定。我更喜欢通过req
和res
进入,因为它对我来说更熟悉。但通过context
也允许访问其他功能,例如context.log
。这里没有对错之分,选择你的冒险并保持一致。
数据访问服务
创建第一个 Azure 函数时,“hello world”函数通常会返回一条静态字符串消息。在大多数 API 中,你都需要与另一个数据库或 Web 服务通信,以获取/操作数据,然后再返回响应。
就我而言,我获取的是英雄列表。因此,我将大部分数据访问服务推迟到一个名为 的 Node.js 模块中hero.service.js
。为什么这样做?简单来说,是为了组织我的代码(在本例中是数据访问服务),使其遵循 DRY(不要重复自己),隔离职责,使其更易于扩展、调试和测试。
该hero.service.js
模块首先获取对容器(包含数据库数据的存储单元)的引用。为什么要抽象它?好问题……我将其抽象为一个共享模块,以便可以重用该逻辑。我需要获取所有类型的容器,而获取容器需要使用一些特定于数据库的连接 API 访问数据库。我们稍后会详细讨论这一点。
该getHeroes
服务接受上下文,并使用解构将响应对象取出并放入一个变量中res
。然后,它尝试获取英雄列表,成功后就将它们添加到响应中。失败时,它会返回一个错误。
// shared/hero.service.js
const { heroes: container } = require('./index').containers;
async function getHeroes(context) {
let { req, res } = context;
try {
const { result: heroes } = await container.items.readAll().toArray();
res.status(200).json(heroes);
} catch (error) {
res.status(500).send(error);
}
}
共享数据库模块
数据访问服务模块hero.service.js
从共享数据库模块导入。这个模块是连接数据库的关键所在。在本例中,我通过 npm 中的 Node.js SDK 使用 Azure 的 CosmosDB。
请注意,代码通过 Node.js 环境变量读取机密信息。然后,它只需从相应的数据库中导出容器即可。我可以使用不同的环境变量,而无需更改代码。
// shared/index.js
const cosmos = require('@azure/cosmos');
const endpoint = process.env.CORE_API_URL;
const masterKey = process.env.CORE_API_KEY;
const databaseDefName = 'vikings-db';
const heroContainerName = 'heroes';
const villainContainerName = 'villains';
const { CosmosClient } = cosmos;
const client = new CosmosClient({ endpoint, auth: { masterKey } });
const containers = {
heroes: client.database(databaseDefName).container(heroContainerName),
villains: client.database(databaseDefName).container(villainContainerName)
};
module.exports = { containers };
你的路线是什么?
我不希望我的 API 出现这种情况,/api/heroes-get
而是更喜欢/api/heroes
在执行GET
操作时这样做,所以我修改了它。我的函数位于路径中/heroes-get/index.js
,在同一个文件夹中有一个function.json
文件。这个文件是配置函数行为的地方。我想要更改的关键是路由别名。请注意,我通过route: heroes
在下面的代码块中进行设置来更改它。
现在我的终点是api/heroes
。
// function.json
{
"disabled": false,
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"],
"route": "heroes"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
重点是什么?
整理代码、隔离逻辑只有在带来切实的积极影响时才能让你的工作更轻松,所以让我们来探讨一下。在编写下一个更新英雄数据的函数时,该函数可能类似于以下代码。
const { putHero } = require('../shared/hero.service');
module.exports = async function(context) {
context.log('putHero: JavaScript HTTP trigger function processed a request.');
await putHero(context);
};
你注意到它和获取英雄的函数非常相似吗?它们之间形成了一种模式,这很好。这里最大的区别在于代码是在模块putHero
中调用的hero.service.js
。让我们仔细看看。
英雄更新逻辑是隔离的。隔离是 的主要工作之一,hero.service.js
获取英雄的逻辑也是如此。
展望未来,删除、插入以及任何其他操作的逻辑也可以放在此模块中,并导出以供其他函数使用。这使得将此结构扩展到其他操作和模型变得相对简单。
// shared/hero.service.js
const { heroes: container } = require('./index').containers;
async function getHeroes(context) {
// ...
}
async function putHero(context) {
const { req, res } = context;
const hero = {
id: req.params.id,
name: req.body.name,
description: req.body.description
};
try {
const { body } = await container.items.upsert(hero);
res.status(200).json(body);
context.log('Hero updated successfully!');
} catch (error) {
res.status(500).send(error);
}
}
更多无服务器
分享你的兴趣,我会在写更多关于无服务器的文章时记住你的兴趣!同时,以下是一些资源,如果你需要一些入门资料,可以参考:
- 您的第一个带有 VS Code 函数扩展的 Azure 函数
- Azure Functions 概述
- 开发者指南
- GitHub Repo 中的示例代码
- 适用于 Cosmos DB 的 JavaScript SDK
致谢
感谢Marie Hoeger审阅本文内容并采纳我的反馈。你一定要在 Twitter 上关注 Marie!
文章来源:https://dev.to/azure/how-do-you-struct-your-code-when-moving-your-api-from-express-to-serverless-functions-30bc