Slide 1

Slide 1 text

Go beyond web development with Firebase Cloud Functions Sofia Huts Software Engineer @ JustAnswer Manager @ GDG Lviv

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

https://firebase.google.com/

Slide 9

Slide 9 text

https://firebase.google.com/

Slide 10

Slide 10 text

Platform-as-a-Service (PaaS)

Slide 11

Slide 11 text

CLIENT WEB SERVER DATABASE

Slide 12

Slide 12 text

CLIENT WEB SERVER DATABASE

Slide 13

Slide 13 text

CLIENT WEB SERVER DATABASE

Slide 14

Slide 14 text

CLIENT WEB SERVER DATABASE

Slide 15

Slide 15 text

WEB SERVER DATABASE CLIENTS

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

But what if I want to.. ● synchronize data from another source, outside the database ● send an email or push notification based on changes that happened in database ● perform heavy operations (e.g. image resizing, video transcoding) ● expose HTTP endpoints

Slide 20

Slide 20 text

Cloud functions for Firebase

Slide 21

Slide 21 text

App + Firebase SDK Firebase Cloud Functions Database Storage

Slide 22

Slide 22 text

Event driven model Function triggered Event emitted Your code executed

Slide 23

Slide 23 text

In other words...all you know tooth fairy abracadabra result Action / trigger / event

Slide 24

Slide 24 text

● Run in serverless environment ● Isolated backend code pieces as “microservice” ● Autoscales up on load, and down to zero ● Node JS code

Slide 25

Slide 25 text

But I am a mobile developer

Slide 26

Slide 26 text

Triggers HTTP Firebase real-time database / firestore Firebase authentication Firebase analytics / crashlytics Cloud pub / sub Cloud storage HTTP

Slide 27

Slide 27 text

What can I do with it?

Slide 28

Slide 28 text

Notify users when something interesting happens https://firebase.google.com/docs/functions/use-cases

Slide 29

Slide 29 text

Post GitHub commits to a workgroup chat room in Slack https://firebase.google.com/docs/functions/use-cases

Slide 30

Slide 30 text

Let’s code!

Slide 31

Slide 31 text

$ npm install -g firebase-tools $ firebase init functions

Slide 32

Slide 32 text

myproject +- .firebaserc # hidden file which help you quickly switch between | # Firebase projects using ‘firebase use’ | +- firebase.json # describes properties for your project | +- functions/ # directory containing all your functions code | +- package.json # npm package file describing your Cloud Functions | # code and all dependencies | +- index.js # main source file for your Cloud Functions code | +- node_modules/ # directory where your dependencies (declared in # package.json ) are installed

Slide 33

Slide 33 text

const functions = require('firebase-functions'); exports.helloWorld = functions.https.onRequest((req, res) => { res.send('Hello World!'); }); functions/index.js

Slide 34

Slide 34 text

const functions = require('firebase-functions'); exports.helloWorld = functions.https.onRequest((req, res) => { res.send('Hello World!'); }); functions/index.js

Slide 35

Slide 35 text

const functions = require('firebase-functions'); exports.helloWorld = functions.https.onRequest((req, res) => { res.send('Hello World!'); }); functions/index.js

Slide 36

Slide 36 text

const functions = require('firebase-functions'); exports.helloWorld = functions.https.onRequest((req, res) => { res.send('Hello World!'); }); functions/index.js

Slide 37

Slide 37 text

const functions = require('firebase-functions'); exports.helloWorld = functions.https.onRequest((req, res) => { res.send('Hello World!'); }); functions/index.js

Slide 38

Slide 38 text

$ firebase deploy

Slide 39

Slide 39 text

const functions = require('firebase-functions'); exports.lastUpdate = functions.database .ref("/messages/{messageId}").onUpdate(event => { const msg = event.data.val(); msg.lastUpdated = new Date().getTime(); return event.data.adminRef.set(msg); })

Slide 40

Slide 40 text

const functions = require('firebase-functions'); exports.lastUpdate = functions.database .ref("/messages/{messageId}").onUpdate(event => { const msg = event.data.val() msg.lastUpdated = new Date().getTime() return event.data.adminRef.set(msg); })

Slide 41

Slide 41 text

const functions = require('firebase-functions'); exports.lastUpdate = functions.database .ref("/messages/{messageId}").onUpdate(event => { const msg = event.data.val() msg.lastUpdated = new Date().getTime() return event.data.adminRef.set(msg); })

Slide 42

Slide 42 text

const functions = require('firebase-functions'); exports.lastUpdate = functions.database .ref("/messages/{messageId}").onUpdate(event => { const msg = event.data.val() msg.lastUpdated = new Date().getTime() return event.data.adminRef.set(msg); })

Slide 43

Slide 43 text

const functions = require('firebase-functions'); exports.lastUpdate = functions.database .ref("/messages/{messageId}").onUpdate(event => { const msg = event.data.val(); msg.lastUpdated = new Date().getTime() return event.data.adminRef.set(msg); })

Slide 44

Slide 44 text

const functions = require('firebase-functions'); exports.lastUpdate = functions.database .ref("/messages/{messageId}").onUpdate(event => { const msg = event.data.val(); msg.lastUpdated = new Date().getTime(); return event.data.adminRef.set(msg); })

Slide 45

Slide 45 text

Pricing

Slide 46

Slide 46 text

Flame Plan $25/month Spark Plan Free Blaze Plan Pay as you go 125K/month 40K/month 40K/month Google services only 2M/month 400K/month 200K/month 5 GB/month $0.40/million $0.0025/thousand $0.01/thousand $0.12/GB Invocations GB-seconds CPU-seconds Outbound networking Cloud Functions

Slide 47

Slide 47 text

Blaze Plan Calculator https://firebase.google.com/pricing/

Slide 48

Slide 48 text

Blaze Plan Calculator https://firebase.google.com/pricing/

Slide 49

Slide 49 text

Resources Invocation time ● 0.7 sec RAM/CPU ● 256 MB ● 400 MHz Invocations ● 10K Compute time (256/1024) x 0.7s = 0.175 GB-seconds per invocation (400/1000) x 0.7s = 0.280 GHz-seconds per invocation 10 000 x 0.175 = 1 175 GB-seconds per month 10 000 x 0.280 = 1 280 GHz-seconds per month Networking 0 GB egress traffic Calculations

Slide 50

Slide 50 text

Billing Metric Gross Value Free Tier Net Value Unit Price Total Price Invocations 10 000 2 000 000 -1 990 000,00 $0.0000004 $0,004 GB-seconds 1 175 400 000 -398 825,00 $0.0000025 $0,0029375 GHz-seconds 1 280 200 000 -198 720,00 $0.00001 $0,0128 Networking 0 5 -5,00 $0.12 $0 Total / Month $0,0197375

Slide 51

Slide 51 text

My own drama

Slide 52

Slide 52 text

https://2017.devfest.cz/

Slide 53

Slide 53 text

https://github.com/gdg-x/hoverboard/tree/hoverboard-v2 @ozasadnyy

Slide 54

Slide 54 text

Firebase console / real-time database

Slide 55

Slide 55 text

Firebase console / real-time database

Slide 56

Slide 56 text

https://github.com/gdg-x/hoverboard/blob/master/scripts/helper/schedule-webworker.js

Slide 57

Slide 57 text

const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(functions.config().firebase); const scheduleGenerator = require('./schedule-generator-helper.js').generateSchedule; exports.scheduleWrite = functions.database .ref("/schedule").onWrite(event => { const schedulePromise = event.data; const sessionsPromise = admin.database().ref('/sessions').once('value'); const speakersPromise = admin.database().ref('/speakers').once('value'); return generateScheduleOnChange(schedulePromise, sessionsPromise, speakersPromise); });

Slide 58

Slide 58 text

Firebase console / real-time database

Slide 59

Slide 59 text

Drama #2

Slide 60

Slide 60 text

Me Ostap ~ 10 min range time

Slide 61

Slide 61 text

Where the changes I’ve made?

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

My plan was ● Start free 300$ trial on GCP ● Use pub/sub, so we can trigger function in GCP ● In Firebase function publish topic “db-updated” ● In Cloud function subscribe for that topic ● In Cloud function use Slack webhook to send messages in channel

Slide 64

Slide 64 text

Firebase GCP $300 free credit

Slide 65

Slide 65 text

const functions = require('firebase-functions'); const admin = require('firebase-admin'); exports.scheduleWrite = functions.database .ref("/schedule").onWrite(event => { if (event.auth.admin) { const topic = createTopic('db-changed'); const data = { changedData: event.data._delta, dataPath: '/schedule', database: functions.config().firebase.databaseURL }; publishMessage(topic, data); } }); Only if Admin changes

Slide 66

Slide 66 text

function createTopic(topicName) { const pubsubClient = pubsub(); const topic = pubsubClient.topic(topicName); if (topic) { console.log(`Topic ${topic.name} already exists.`); return topic; } pubsubClient.createTopic(topicName) .then((results) => { const topic = results[0]; console.log(`Topic ${topic.name} created.`); return topic; }); }; const pubsub = require('@google-cloud/pubsub'); Returns topic object if exists Create topic for pub / sub

Slide 67

Slide 67 text

function publishMessage (topic, data) { // Create a publisher for the topic (which can include additional batching configuration) const publisher = topic.publisher(); // Publishes the message as a string, e.g. "Hello, world!" or JSON.stringify(someObject) const dataBuffer = Buffer.from(JSON.stringify(data)); return publisher.publish(dataBuffer) .then((results) => { const messageId = results[0]; console.log(`Message ${messageId} published.`); return messageId; }) .catch((err) => { console.error(`An error occured during publish: ${err}`); }); } Accepts only encoded string Publish to pub / sub

Slide 68

Slide 68 text

Subscribe in GCP

Slide 69

Slide 69 text

Subscribe in GCP exports.subscribe = function subscribe(event, callback) { // The Cloud Pub/Sub Message object. const pubsubMessage = event.data; let data = Buffer.from(pubsubMessage.data, 'base64').toString(); let dataObj = JSON.parse(data); postToSlack(dataObj).then(() => { console.log("Message sent"); }).catch(error => { console.error(error); }); // Don't forget to call the callback. callback(); };

Slide 70

Slide 70 text

Post to Slack const rp = require('request-promise'); function postToSlack(data) { return rp({ method: 'POST', uri: 'https://hooks.slack.com/services/....', body: { text: `:fire: Hello from GCP! New changes arrived in ${data.dataPath}, attachments: [ { title: `Check ${data.database}`, title_link: `${data.database}${data.dataPath}`, text: `${JSON.stringify(data.changedData)}`, color: "#FFC107" } ] }, json: true }); }

Slide 71

Slide 71 text

Post to Slack

Slide 72

Slide 72 text

No content

Slide 73

Slide 73 text

Google Cloud functions Functions for Firebase == billing billing == SDK SDK

Slide 74

Slide 74 text

const functions = require('firebase-functions'); const admin = require('firebase-admin'); exports.scheduleWrite = functions.database .ref("/schedule").onWrite(event => { if (event.auth.admin) { const topic = createTopic('db-changed'); const data = { changedData: event.data._delta, dataPath: '/schedule', database: functions.config().firebase.databaseURL }; publishMessage(topic, data); } });

Slide 75

Slide 75 text

const functions = require('firebase-functions'); const admin = require('firebase-admin'); exports.scheduleWrite = functions.database .ref("/schedule").onWrite(event => { if (event.auth.admin) { const data = { changedData: event.data._delta, dataPath: '/schedule', database: functions.config().firebase.databaseURL }; postToSlack(data).then(() => { console.log("Message sent"); }).catch(error => { console.error(error); }); } });

Slide 76

Slide 76 text

Drama #3

Slide 77

Slide 77 text

What if I... ● Want to have backups of my DB ● Don’t wanna enable Blaze plan ● My DB is quite small, so I can store data for FREE

Slide 78

Slide 78 text

5 Gb Free DB Function Storage

Slide 79

Slide 79 text

Let’s code!

Slide 80

Slide 80 text

const functions = require('firebase-functions'); const fse = require('fs-extra'); exports.saveBackup = functions.database.ref('/{node}').onWrite(event => { const maxBackupsCount = 10; const data = event.data.val(); fse.writeJson('/tmp/db-data.json', data) .then(() => { console.log('Successfuly written data to /tmp folder') }) .catch(err => { console.error(err) }); const gcs = require('@google-cloud/storage')(); // Reference to an existing bucket. const bucket = gcs.bucket('.appspot.com');

Slide 81

Slide 81 text

//Check files count. If get to limit - delete the oldest one bucket.getFiles({ prefix: 'backup/db-data'}).then((result) => { let files = result[0]; if (files.length < maxBackupsCount) { return; } else { // sort by date desc - oldest first const sortedFiles = files.sort(function(a,b) { return new Date(a.metadata.updated) - new Date(b.metadata.updated); }); const fileNameToDelete = sortedFiles[0].name; bucket .file(fileNameToDelete ) .delete() .then(() => { console.log(`${fileNameToDelete} deleted.`); }) .catch(err => { console.error('ERROR:', err); }); } }) .catch(err => console.error(err));

Slide 82

Slide 82 text

//Upload a local file to a new file to be created in bucket. bucket.upload('/tmp/db-data.json', { destination: `/backup/db-data-${new Date().getTime()}.json` }) .then(() => { console.log('Backup was successfuly uploaded!'); }) .catch((err) => { console.error(`Error occured during upload: ${err}`); }); }); //End of saveBackup functions for Firebase

Slide 83

Slide 83 text

To sum up ● Let the Cloud functions help you, delegate your work ● Try to write optimised function as much as you can (time of execution matters, you pay for it) ● Use GSP free tier with 300$ trial, and it will apply to your Firebase project as well ● Enjoy this magic!

Slide 84

Slide 84 text

Thank you! Questions? @sophie_h29 sophie.huts.7