The way it seems Google Cloud Functions works is by:
functions
directoryfunctions
directory t
I'm using modofun (https://modofun.js.org), which is a router for multiple operations based on the request path. This allows me to gather related functions into a module that's deployed as a single Google Cloud Function. The dependencies for all functions in that module are the same, so that makes it dependency-efficient. And you can also share common global resources, like database connections.
I agree that deploying every single function on its own is a waste of resources, and it's very hard to maintain.
I did this for a Google Cloud Functions deployment I have in production.
Rather than exporting each Cloud Function individually, we can export all of them at once by requiring a file that export's every file in a specific directory and its subdirectories.
functions/index.js'use strict';
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const logging = require('@google-cloud/logging')();
const stripe = require('stripe')(functions.config().stripe.token);
admin.initializeApp(functions.config().firebase);
module.exports = require('./exports')({
admin, functions, logging, stripe
});
We can create a folder for each provider, e.g. auth, database, https, in which we can organise the associated resource's events such as auth.user.onCreate
or database.ref.onWrite
.
By structuring our files this way with an event in each file, we can search for the function files and use the file path to dynamically create the Cloud Function's name and export it.
e.g. functions/exports/auth/user/onCreate.f.js -> onCreateAuthUser
'use strict';
module.exports = (app) => {
const glob = require('glob');
glob.sync('./**/*.f.js', { cwd: __dirname }).forEach(file => {
const only = process.env.FUNCTION_NAME;
const name = concoctFunctionName(file);
if (only === undefined || only === name) {
exports[name] = require(file)(app);
}
});
return exports;
}
function concoctFunctionName(file) {
const camel = require('camelcase');
const split = file.split('/');
const event = split.pop().split('.')[0];
const snake = `${event}_${split.join('_')}`;
return camel(snake);
}
Finally, our function files can use the argument passed to access commonly required services such as Firebase Admin and Functions, and even Stripe.
functions/exports/auth/user/onCreate.f.js'use strict';
module.exports = ({ admin, functions, stripe }) => {
return functions.auth.user().onCreate(event => {
const { email, uid } = event.data;
const ref = admin.database.ref.child(`user-customer/${uid}`);
return stripe.customers.create({
email, metadata: { uid }
}).then(customer => {
return ref.set(customer);
});
});
}