Reputation: 756
After researching the Firebase docs, videos, StackOverflow, lots of articles, ... organizing multiple (lots of) cloud functions in an "easy" way is not obvious. Especially, since the official Firebase docs do not provide a clear vision/recommendation. Actually, the real problem is the lack of clear documentation on how to setup a Firebase project with lots of functions right from the beginning.
I am trying to find an easy approach considering the following points:
Coming from manually deployed cloud functions to a simple initial CLI setup using JavaScript, I tried the following file structure with some success:
[project]/
- functions/
- index.js
- src/
- functionA.js
- functionB.js
- ...
...
Structure based on the official docs: 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;
Using https://gist.github.com/saintplay/3f965e0aea933a1129cc2c9a823e74d7#file-index-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 = ...
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()
a clean implementation?Upvotes: 4
Views: 2383
Reputation: 2477
I will try to answer your questions all together:
Organizing your functions files and folders is a matter of opinion. You can always require other functions to your index.js using the require() method. You don't need to follow the official documentation regarding this. check out this nice solution from another stackoverflow question
what you need to give attention to is not to include require statements in the global scope that other functions doesn't use. like if you have a function that use a Nodejs library and another function doesn't use this library and you required this library in the global scope then the cold start to fetch the library will effect both functions:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
// need the admin sdk
exports.functionA = ...
// doesn't need the admin sdk
exports.functionB = ...
in the example above the cold start to get the admin sdk will apply on both functions. you can optimize it by:
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 = ...
We required the admin sdk only in the function that needs it to reduce cold start and optimize memeory usage.
you can even improve this more by using dynamic imports in typescript which will fetch the library or the module code at the time of invocation rather than the static import with require in JS.
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
}
...
})
regarding:
try { admin.initializeApp() } catch (e) {console.log(e);}
I believe this is a valid implementation and this will check for the error:
The default Firebase app already exists. This means you called initializeApp() more than once without providing an app name as the second argument. In most cases you only need to call initializeApp() once. But if you do want to initialize multiple apps, pass a second argument to initializeApp() to give each app a unique name.
unless you want to do something with this error i suggest using the implementation above with a boolean flag:
is_admin_initialized = false
regarding cold start, it's unavoidable evil in serverless architecture and there is no way to eliminate it at the time being but you can reduce by following different practices:
1- you should expect more or less around 8s of cold start for JS functions with 1 or 2 dependencies but that all depend on the end on these packages size.
2- cold start can happen in different cases such as:
3- don't include libraries (dependencies) that your functions don't need and scope the dependenices only to which functions need them using either static import with require() in JS or dynamic async import with TS. Remember, sometimes you don't need to use the whole library but only one function of it. In this case, try to import just that function from the library rather than the whole thing.
Upvotes: 1