dimib
dimib

Reputation: 756

Easy Firebase Cloud Functions organization

Problem

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:

Solution?

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
    - ...
...

index.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;

FunctionA.js

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 = ...

FunctionB.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 = ...

Question(s)

Upvotes: 4

Views: 2383

Answers (1)

Methkal Khalawi
Methkal Khalawi

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:

  • your function has not been triggered yet let's say after your function deployment
  • your functions instances has been shut down and there are no idle instances to receive the upcoming request Cloud Functions in my experience can keep instances idle for few minutes and around 10m more or less.
  • your functions is autoscalling and provisioning new instances

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

Related Questions