How do Cloud Functions work? | Get to know Cloud Firestore #11

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
TODD KERPELMAN: Serverless computing, it's all the rage these days, kind of like unicorn-flavored food or the return of the fanny pack. But unlike those other trends, I think serverless computing is going to be around for a while. Because the idea that you can run a complete working app without ever needing to set up a server of your own, that's pretty neat. Now, there are a lot of pieces involved in a serverless computing setup, but I think from Cloud Firestore's perspective, there are two big pieces. First, you've got the security rules, the bouncer at the door to make sure those requests coming in from the client are legit. This lets you query the database directly from the clients, and we've talked about this a lot in an earlier video, so go back and watch that one if you haven't. But there will still be times you're going to want to run some kind of logic on the back end, and that's where Cloud Functions come in. They let you run what is essentially server-side code without actually needing to set up a server. So let's talk about Cloud Functions and how they can make your Cloud Firestore implementation more effective. [MUSIC PLAYING] Oh, I hate when they add the fake unicorn flavoring. Do you want any of this? No? All right. I'll save it for later. So let's talk about Cloud Functions and why it's sometimes important for you to run code on the back end instead of putting all that logic in your clients. Often, it's for security. There are times when you just can't trust clients to make the changes you want, or the security rules required to support making these changes from the client would just be too convoluted. Sometimes, going with the server-side function is just plain easier. Right? Like, if you've got business logic that you always want to run on certain bits of data, it's often a heck of a lot simpler to write that logic server side and deploy it immediately, rather than try and ship out updates to like your mobile and web clients all exactly at the same time. And sometimes, it's to perform database-wide maintenance logic that would be weird to perform on a client, even if you could work out all those other security issues. Now Cloud Functions is a pretty weighty topic, and I can't cover everything in one 15-minute video. So if you really want all the details on how to get started implementing Cloud Functions, I highly recommend you check out our other series on our YouTube channel, and we willing link to that below, but let's see if we can give you the executive summary here. Generally speaking, Cloud Functions are functions that sit around in the cloud and are activated when a particular event happens in your Google Cloud or Firebase project. For those of you building Cloud Firestore-powered apps, this is going to be like when a document is created or changed in your database. But it can be for other things as well, like if a new user account is created in Firebase Auth, or a conversion event has been fired in analytics. And they can sometimes be in response to a direct HTTP request which is one way you can use Cloud Firestore to power like a dynamic website or even create your own API for your database, but we'll talk more about that later. Now, when these events happen, Cloud Functions kind of spins up a little server instance in the cloud, in which your function is activated and asked to do some kind of work in response to these events, when they happen. Very often, this will be performing some kind of calculation based on the incoming data and then writing or changing information elsewhere in Cloud Firestore. But you could also do things like send a notification or save a newly generated image to cloud storage or pin your support team Slack channel. Now, you'll notice most of these final tasks involve pinging other cloud-based services, which means they're not going to happen immediately. Right? It still takes some amount of time for your event handler to talk to Cloud Firestore or FCM or whatever other service you're calling out to. And in the meantime, Cloud Functions is sitting there, monitoring your function, waiting for it to finish, so it can like shut down the environment and reuse those resources. So how does it know when all your calls are done? Most of the time, you're going to have to do this by having your function return a promise. A promise is basically a JavaScripty way of saying, this is a process that's going to do some work, and when the work is done, I'm going to change my status either to complete or failed. In other words, I promise, I will resolve the status of this object, once the work is done. Or maybe to put it another way, it's our functions way of saying, wait, don't terminate me. Yet I still have so much to live for, or at least one database call's worth. Now, generally speaking, returning a promise from a Cloud Function, it's pretty easy. Because when you perform an action, like writing to the database, that call is already set up to return a promise. So you really just need to make sure you're capturing the promise that's being returned from your database write, or what have you, and returning that within your function. Now, maybe this is a good time to point out there are actually two flavors of Cloud Functions. You've got Google Cloud Functions which is essentially the Google Cloud Platform product that's kind of running this whole thing behind the scenes. Cloud Functions for Firebase is, to oversimplify a little bit, a wrapper around Cloud Functions that makes deploying Cloud Functions and working with Firebase products easier. The biggest drawback is that, while Cloud Functions, the Google product, supports several different languages, including Python and Go, Cloud Functions for Firebase only supports TypeScript and JavaScript with no JS. So if you're coming here from the Firebase world, and don't mind writing the occasional JavaScript, I would recommend using Cloud Functions for Firebase, as it generally makes things fairly easy. However, if you're coming here from the Google Cloud Platform world, or you're a Firebase developer who hates JavaScript with a burning passion of 1,000 suns and doesn't mind a more complex to deployment process, maybe go with Google Cloud Functions. Now, for the rest of this video, I'm mostly going to be talking about Cloud Functions for Firebase, because they're the ones that gave me this T-shirt, but most of these lessons should still apply to both scenarios. So to use a semi-realistic example, let's take a look at my restaurant review app here, and let's suppose that I want to remove any bad language that my users might be submitting in their reviews. So that's swear word, swear word, swear-- I'm going to check Urban Dictionary for this one. Whoa, how does something so cute have so much hate in its heart? Since this is a family-friendly video-- hi, kids-- I won't use any actual bad language. Let's just replace the words fat-free cheese-- which I think we can all agree is an abomination-- with just cheese. Obviously, I can't really count on the client to fix this for me. Sure, some client-side code might prevent some casual swearing, but you have to remember that clients are untrustworthy by nature. Any halfway decent hacker could modify their client or network calls to sneak in this undesirable phrase. So if I really want to be sure no bad words are making it into my reviews, I need that server side with the Cloud Function. So let's see what that looks like. So because I'm using Cloud Functions for Firebase, which runs using no JS, I would be writing this thing in JavaScript, or more likely TypeScript which you can kind of think of is like strongly-typed JavaScript, which I prefer. And in fact, even if you've never written a single line of TypeScript before, I recommend starting with that as your language of choice. Not only does the language itself have some pretty nifty features, but often, your IDE and/or linter can analyze your code and catch some common errors, particularly around dealing with promises. So give it a try if you haven't. So generally speaking, after doing a little bit of setup work, like importing Firebase Functions, you're going to start by defining a function that looks a little something like this. You can see that I'm using the onCreate handler here to handle cases where a new document is created, but this could also be onUpdate or onDelete or onWrite which covers all three of those cases. Now, we don't want this event to fire anytime a document is created anywhere in our database, just when a review is added. So our document method takes a path argument that specifies which documents we care about. You can see that our path looks very similar to how paths work in security rules. By adding a string in brackets here, we're basically saying, hey, make this a wild card that matches any component in our path but then store that value in a variable named, in this case, Review ID that it could access later. So this onCreate event handler takes in two arguments. You've got the snapshot which contains information about the document that's being added and the context which includes some other data about the function, like those path wildcards we just captured. Now, I'm going to get the fields of this document by calling snapshot.data, and I'll want to check to make sure my data isn't null. And then I can grab the text from the data, and then finally, I can sanitize it by calling whatever custom function I want on the text field of this document. Then, I can set the new value by taking snapshot.ref-- that's the reference to the document that was just created-- and calling update on it with this new text. Now, there are three notes I want to point out here. First, this call to Cloud Firestore is something that's happening asynchronously. Right? It's making a call to another resource in the cloud. So this update call immediately returns a promise. Now, if I wanted, I could chain on additional work to this promise by adding dot then which is basically my way of telling my code, OK, do more work after this promise is resolved. But in my case, I have no more work to do, so I'm just going to return this promise. And that will ensure that my function stays alive long enough to update my document, which will then resolve this promise. Incidentally, you can see that TypeScript is warning me that I'm not returning anything here if review data is null. So I'll fix that by explicitly returning null in a little else block here. Second, I don't have to limit my document right to just this document. By using the Admin SDK, which is basically our server-side library for talking to Firebase resources, I could write to any document in my Cloud Firestore database just by calling admin.firestore.doc and entering a path to my document. This library also lets me do things like talk to the real-time database or send a notification. Finally, those of you out there who are paying close attention might have realized that this function only activates if I create a new review, but my users probably have the ability to edit their reviews too. So really, we want to sanitize this text anytime a user creates or edits a review. So we can do that either by writing a separate onUpdate event handler or, in this case, just creating an onWrite handler which combines those two cases. Now, onWrite and onUpdate work almost the same as onCreate. The biggest difference is that, instead of being passed a single document snapshot to represent the document being written, I now have a Change object, and this object contains two document snapshots-- Before to represent the object that was there before and After to represent the object post change. In my case, I'm really only interested in the After object. That represents the text after the review has been created or after the edit has been made. So with a few small changes, I can now make this function work both for new reviews and for edits, but there's one more teeny, tiny, little problem here. I have now introduced an infinite loop into my function. Now, infinite loops are generally considered to be a terrible thing. In fact, for more important information on infinite loops, make sure you follow the link below. The issue here is that, when my Cloud Function writes its sanitized version to the database, that write is triggering a brand new onWrite event. And that's going to wake up this function, which is going to rewrite the sanitized version of my sanitized version to the database, which will trigger a new onWrite event, and so on and so forth forever. Now, there are a few ways to fix this. I think the simplest would just be to add a simple check here that says, hey, did my sanitize text actually change from the original? If it did, write it to the database. Otherwise, return null. By returning null, I won't create another write event, and this function won't trigger again. And in fact, let me go in and change one of these reviews here to mention the fat-free cheese pizza. I can make this change directly in the Firebase console, and you can see that almost immediately Cloud Functions has fixed my terrible culinary mistake. So I fixed that infinite loop issue here, but this is something you should watch out for, particularly when you're dealing with any onUpdate or onWrite handlers that write back to the same document that was changed. Now, when you write Cloud Functions, you're generally using our server SDK, or what we like to call the Admin SDK, to perform a lot of these Cloud Firestore actions. It works very similar to the client libraries you're probably used to but with a couple of key differences. For one thing, transactions work a little differently. Unlike the client libraries, these perform more traditional lock down the database kinds of transactions, and you can do things like perform actual queries inside of them. But probably the biggest difference from the Cloud Firestore perspective is that the Admin SDK is not subject to the same security rules that you have written for your project. Which if you think about it makes sense, right? Like, you can't trust a request from any client that comes in, because who knows where that client's been or who's hacked it? But Cloud Functions, those are run in a trusted environment which means, much like a cop-out for revenge in an '80s movie, the rules don't apply to them. Esposito, I might be your chief, but I'm also your friend. You're running too hot, and you're going to burn up. And this is often a good thing, as you will see you in the next video. But do keep in mind, this also means your validation rules are getting ignored as well. So like, test your functions, and make sure they really are setting data in the format that you are expecting. So let's say I were to actually deploy this function using Firebase. What would happen? Well, there's a little work the Firebase command line tool does to take my code and convert it into the little bits of serverless magic that are needed for it to run in Google Cloud Platform land. For starters, the way Cloud Functions are architected, every function that responds to an event lives in its own separate container. So even if I end up writing all my functions in one file, the first thing the Firebase command line tool does is analyze my file and create as many containers for as many individual functions that it needs to. In my case, I only have this one clean up new review function, but if I had say like a calculate average score function, that would exist in its own separate container. The command line also does the heavy lifting of creating the equivalent of those individual deploy commands for each one of these functions. If you were using Google Cloud Functions, you'd need to create commands like this in order to deploy each visual function, but the Firebase deploy tool, it handles this for you. Finally, Firebase does some work to make sure each one of these functions has all of your environment variables set up properly. So that when you call Initialize App on the Admin SDK, your Cloud Function has access to your database without your needing to explicitly add any of your project configuration information, which is nice. So then, later on, when a user writes a new restaurant review, Cloud Functions will go ahead and spin up a server instance that contains my clean up reviews function. It will then go ahead and load up node and the Admin SDK and configure my environment variables, and then it will run my function in response to this event. And so understanding all this might explain some the quirks you might notice when you're dealing with Cloud Functions. In fact, let me hit you up with the top five quirks that you will probably run into. OK, quirk number one, performance might not always be what you expect. The very first time you run a function is when it's going to take the longest. This is because Cloud Functions needs to start up that whole server instance and load in all of your libraries and dependencies in order to run your function. This is known as a cold start, and it's often quite slow. But the more often your function is being called, the more likely Cloud Functions will keep the server spun up and ready to go. Which means that, in general, the busier and more popular your app is, and the more consistent your traffic, the faster your Cloud Function will probably be, which I know is a little counterintuitive. The flip side of this is that you, as a developer, will encounter cold starts a lot, probably about 100 times more often than your customers, so yay, lucky you. For example, the first time I tested that review sanitizing function, it took a second or two after deploying. But the second time, it took about 60 milliseconds, so you got a big difference there. OK, quick number two, global variables can be a little weird. Because each of these functions are essentially being run in their own environments, you can't expect them to share any state between them. If my two Cloud Functions both use the same global variable, for instance, that global variable would live separate lives on each server. Function B wouldn't know what's happening with function A's global variable. In fact, if my function is very popular, I might have multiple servers all handling function A, and each of their global variables are going to be different too. But on the flip side, if a function does get called repeatedly on the same server instance, and that server instance hasn't been reclaimed, then your global variable will persist from one function call to the next. So it's a little unpredictable. OK, quirk number three, speaking of global variables, I should probably point out that any global variables that you create-- and this includes things helper functions or global variables that reference libraries-- will essentially be loaded up in every single Cloud Function container, whether you need it or not. So like, if I were to leave in that Admin SDK, that would get loaded up in the container for my clean up review function but also any future like welcome new user function or calculate restaurant review average function or any other function I write. Now, in this specific case, this is probably fine, since most of these functions are going to need the Admin SDK, but imagine that one of my functions needed to load up some really heavy math or an ML library or something. In this case, it probably doesn't make sense to be loading that library in the global namespace, like this, because it'll get loaded up by every other function instance out there, and that will hurt all of their cold start times. So instead, I'd probably want to lazily load it inside my function itself, and there are ways to do that, and I would go ahead and check out Doug's video series exactly on that topic. OK, quirk number four, since these things are off on their own little container, subject to the whims of the internet and/or cold start times, there's no guarantee that functions will be performed in the order that these events were received. Now, most of the time, this shouldn't be a big deal, but double check your assumptions. Like, if my app, for instance, automatically created a user document after a user created a new account, I might expect that my user has created an account function would fire before my document was written function. And most of the time, that will be true, but not always. So as with most things in life, you should generally be OK, as long as you code defensively. Watch out for any assumptions that your functions might be making and like fail gracefully. Quirk number five is that function sometimes can be called more than once for the same event. It's not something that happens often, but it does happen, and you need to make sure something catastrophic doesn't occur in those cases. Now, one thing that does help this, though, is the event ID property. This is a unique identifier that's sent as part of the context parameter with each event, and so if a function does get triggered twice, each of those instances will have the same event ID. Now, you can take advantage of this in several ways. Some services, like the stripe payment API, let you pass an item potency key, essentially, a unique identifier along with every call, to make sure that a user doesn't get charged twice. This would be a great situation to use the event ID, but honestly, duplicate functions in item potency are big enough topics that I don't think I can really cover them here. So again, check out Doug's series or the Cloud Functions documentation, if you want to find out more. Hey, haven't I seen you there already? Oh, deja vu. But hey, I think we spent a lot of time talking about Cloud Functions in the abstract, when we want to use them in practice, beyond eliminating fat-free cheese from the earth. Well, I'm going to cover five common scenarios for making use of Cloud Functions, and we will go over those in the next video. No, don't you walk away. Come back here, Esposito. Esposito! [MUSIC PLAYING]
Info
Channel: Firebase
Views: 67,752
Rating: 4.9495354 out of 5
Keywords: Cloud Functions, Firebase Cloud Functions, Google Cloud Functions, Cloud Firestore, Get to Know Cloud Firestore, Todd Kerpelman, infinite loops, understanding cloud functions, cloud functions for firebase, firebase, firestore, firebase developers, google developers, google, firebase 2020, google 2020, google developers 2020, firebase developers 2020, GDS: Yes;
Id: rERRuBjxJ80
Channel Id: undefined
Length: 18min 20sec (1100 seconds)
Published: Mon Jan 13 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.