问题
在研究了Firebase文档、视频、StackOverflow、大量文章后。。。将多个(许多)云功能组织在"云"中;"容易";方式并不明显。特别是,由于官方的Firebase文档没有提供清晰的愿景/建议。事实上,真正的问题是缺乏关于如何从一开始就设置具有大量功能的Firebase项目的明确文档。
考虑到以下几点,我正试图找到一种简单的方法:
- 基于Firebase文档(每个新的Firebase用户都在阅读)
- Firebase CLI部署的单一index.js要求
- 在TypeScript上使用纯JavaScript,没有太多语法魔法糖
- 对index.js中包含/引用的文件/函数进行透明控制
- 开发了很多函数,但不要直接在index.js中导出所有函数(有点像部署控制层)
- 没有太多像本例中那样的异步/等待导入https://medium.com/firebase-developers/organize-cloud-functions-for-max-cold-start-performance-and-readability-with-typescript-and-9261ee8450f0
- 不依赖于像这个好例子中那样的命名约定https://codeburst.io/organizing-your-firebase-cloud-functions-67dc17b3b0da
- 函数数量的可伸缩性
- 从初始设置(使用Firebase文档的初学者)到高级设置,如何迁移结构
- 尽可能减少添加/部署越来越多云功能的工作量
- 性能:懒惰地加载/初始化管理SDK+确保它只初始化一次
- 最小化服务器实例冷启动时间
- 易于解释,易于使用,易于维护(当然这有点主观)
解决方案
从手动部署的云功能到使用JavaScript的简单初始CLI设置,我尝试了以下文件结构并取得了一些成功:
[project]/
- functions/
- index.js
- src/
- functionA.js
- functionB.js
- ...
...
index.js
基于官方文件的结构:https://firebase.google.com/docs/functions/organize-functions
const functions = require('firebase-functions');
const functionA = require('./src/FunctionA');
exports.FunctionA = functionA.FunctionA;
const functionB = require('./src/FunctionB');
exports.FunctionB = functionA.FunctionB;
FunctionA.js
使用https://gist.github.com/saintplay/3f965e0aea933a1129cc2c9a823e74d7#file-索引js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
// Prevent firebase from initializing twice
try { admin.initializeApp() } catch (e) {console.log(e);}
exports.FunctionA = ...
函数B.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
// Prevent firebase from initializing twice
try { admin.initializeApp() } catch (e) {console.log(e);}
exports.FunctionB = ...
问题
- 这是一个切实可行的解决方案吗
- 与异步/等待导入相比,是否存在任何性能问题
- 围绕
admin.initializeApp()
的try-catch块是一个干净的实现吗 - Typescript等效解决方案是什么
- 冷启动时间:当通过Firebase CLI进行部署时,我在谷歌云控制台中注意到";云功能";所有云函数实例都包含所有其他函数的源代码的部分
- 在云控制台中手动创建函数时,每个函数只有自己的一段代码要部署/初始化/执行
- 有关于优化的建议吗?(例如index.js的可维护性)
我会一起回答你的问题:
组织您的功能文件和文件夹是一个意见问题。使用require()方法,您可以随时向index.js请求其他函数。你不需要遵循官方文件。从另一个stackoverflow问题中查看这个不错的解决方案
您需要注意的是,不要在全局范围中包含其他函数不使用的require语句。比如,如果你有一个使用Nodejs库的函数,而另一个函数不使用这个库,并且你在全局范围内需要这个库,那么冷启动获取库将影响这两个函数:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
// need the admin sdk
exports.functionA = ...
// doesn't need the admin sdk
exports.functionB = ...
在上面的示例中,获取admin-sdk的冷启动将应用于这两个函数。您可以通过以下方式进行优化:
const functions = require('firebase-functions');
// need the admin sdk
exports.functionA = functions.https.onRequest((request, response) => {
const admin = require('firebase-admin');
...
})
// doesn't need the admin sdk
exports.functionB = ...
我们只在需要admin-sdk的功能中需要它来减少冷启动并优化内存使用。
您甚至可以通过在typescript中使用动态导入来改进这一点,该导入将在调用时获取库或模块代码,而不是在JS中使用require进行静态导入。
import * as functions from 'firebase-functions'
// this variable will help to initialize the admin sdk once
let is_admin_initialized = false
export const functionA =
functions.https.onRequest(async (request, response) => {
// dynamically import the Admin SDK here
const admin = await import('firebase-admin')
// Only initialize the admin SDK once per server instance
if (!is_admin_initialized) {
admin.initializeApp()
is_admin_initialized = true
}
...
})
关于:
try { admin.initializeApp() } catch (e) {console.log(e);}
我相信这是一个有效的实现,这将检查错误:
默认的Firebase应用程序已经存在。这意味着你打电话initializeApp()多次,但未提供应用程序名称作为第二个论点。在大多数情况下,您只需要调用initializeApp()一旦但如果你确实想初始化多个应用程序,请稍等initializeApp()的参数,为每个应用程序提供一个唯一的名称。
除非你想处理这个错误,否则我建议使用上面带有布尔标志的实现:is_admin_initialized = false
关于冷启动,在无服务器体系结构中,这是不可避免的邪恶,目前还没有办法消除它,但你可以通过以下不同的做法来减少:
1-对于具有1或2个依赖项的JS函数,您应该预计大约8秒的冷启动时间,但这些依赖项都取决于这些包的大小。
2-冷启动可能发生在不同的情况下,例如:
- 您的函数尚未被触发,比如说在您的函数部署之后
- 您的函数实例已关闭,没有空闲实例可以接收即将到来的请求根据我的经验,云函数可以让实例闲置几分钟,大约10米左右
- 您的功能是自动调用和配置新实例
3-不要包括函数不需要的库(依赖项),并使用JS中的require()静态导入或TS动态异步导入将依赖项限定为函数需要的依赖项。记住,有时你不需要使用整个库,只需要使用其中的一个函数。在这种情况下,试着从库中只导入那个函数,而不是整个函数。