Extensibilty through Webhooks Part 3: Google Cloud
Tags: tutorial, aws, gcloud
Published: 2020-06-08 12:56:02

Now that we've had an opportunity to see howe we can implement webhooks using [Amazon Web Services](https://dev.to/brandinchiu/extensibilty-through-webhooks-part-2-amazon-web-services-35hg), we're going to replicate the process with Google Cloud.  Much of the process will be very similar -- only using Google's version of the same or similar services to what we used with AWS.

> Note: just like with AWS, the features we will be working with may require a credit card on file with Google Cloud to activate.  However, our testing should not reach a level that would invoke actual charges on your account.

### How it works
The Google Cloud products we will be using for this demonstration will be: _[Cloud Functions](https://cloud.google.com/functions)_, and _[Cloud Pub/Sub](https://cloud.google.com/pubsub)_.

_Cloud Functions_ is Google's version of Lambda, and allows for us to execute serverless code running on Google Cloud's hardware.  We will also be using node for our implementation, but _Cloud Functions_ supports Go, Java, and Python as well.

We will be connecting our _Cloud Function_ to a _Cloud Pub/Sub topic_, which will be how we invoke our function.

_Cloud Pub/Sub_ is a messaging service for Google Cloud, and functions in a similar way to Amazon's _Simple Queue Service_, only with less focus on the queueing functionality (which we will not be needing anyway).

### Step One: Create a Project
Google Cloud manages resources differently from Amazon Web Services.  In AWS, resources are defined globally on the account and segmented by regions.

In Google Cloud, resources are managed in _projects_.  A project is a separate entity within your Google Cloud account, and any resources you use (compute, networking, storage) are only available within that project.

That means, in order to start working with _Cloud Pub/Sub_ and _Cloud Functions_, we first need to a project.

If you've never worked with Google Cloud before, you will be prompted to create a project when you start.  If you already have an account, you can create a new one by clicking the dropdown menu next to the page title and selecting "New Project" in the top-right corner of the dialog.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nbn5orcnzw6pwan8k0uv.png)

Give your project a name, and then click "Create".  Once your project finishes being initialized, you'll be greeted with your new project's dashboard.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/5hyezrbyaygc0ii1jmhs.png)

We're now ready to begin!

### Step Two: Create the Pub/Sub Topic
In order to trigger our webhooks to fire, we'll be making use of _Cloud Pub/Sub_ as a messaging bus.  Messages sent to _Pub/Sub_ are organized using "topics".

Open up the side navigation in Google Cloud and launch _Pub/Sub_ (located under the "Big Data" header).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/sb815w8bzdpua8av8yv4.png)

next, select "Create Topic" from the _Pub/Sub_ landing page to create our webhooks topic.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/18g8l8revhpvx09v82so.png)

Give your topic a name, and leave the encryption options as the default.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nm549wyc0rs0qezt0oyh.png)

Your topic will take a few seconds to be generated, and then you'll see the topic dashboard.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jp2zdc8qyei5gjw0pm69.png)

> Note: it's so common for _Pub/Sub_ to be connected to a _Cloud Function_ that there are helpful shortcuts built into the page near the top! 

### Step Three: Configuring our Cloud Function
Now that our topic has been created, and we have a way to dispatch messages through our Google Cloud infrastructure, we need to build our message consumer so we can actually do something with those messages.

Open our side menu back up and navigate to _Cloud Functions_ (located under the "Compute" header).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/c7xrn7aya8m5oq6tbfgm.png)

You'll be dropped on the empty list page for your project's _Cloud Functions_, so let's create one now.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zed9fxcosubc7ld3f3na.png)

Start by giving your function a name, and then leave the "Memory Allocated" as the default value of 256 MB.

Next, change the value for "Trigger" from "HTTP" to "Cloud Pub/Sub", and then select the topic we created earlier.

Leave the rest of the values as their defaults, and click "Create" at the bottom.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/u56ddx70ryz26gmbjao6.png)

Your function will take a few minutes to prepare while Google Cloud initializes the environment.  Once it's done, open up the function to review it's dashboard.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/b5ga07iqdg1mfuee9r2m.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/3s4lt79ul21tfxyjdufd.png)

At this point, our function is still using the default "Hello Pub/Sub" function it was created with.  We'll need to update the function to send our webhook instead.

Just like with AWS, we'll use a very simple node script to push our requests to our webhook endpoints.

Our first step will be to modify the package.json file.  The end result should look like this:

```json
{
  "name": "dispatch-webhooks",
  "version": "0.0.1",
  "dependencies": {
    "@google-cloud/pubsub": "^0.18.0",
    "node-fetch": "^2.6.0"
  }
}
```

We'll leave the _Pub/Sub_ dependency that's already there, and add our "node-fetch" package.

Next, we'll update our source code using the following simple script:

```javascipt
const fetch = require('node-fetch');

exports.dispatchWebhook = (event, context) => {
  const endpoint = event.attributes.endpoint;
  const eventName = event.attributes.event_name;
  const data = JSON.parse(Buffer.from(event.data, 'base64').toString());
  
  const headers = {
    "Content-Type": "application/json"
  };
  
  fetch(endpoint, { method: 'POST', headers: headers, body: JSON.stringify(data) })
    .then((response) => {
    	console.log("Dispatched webhook for [" + eventName + "]: " + response.status);
  	})
  ;
};
```

This script doesn't actually do very much.  It pulls some event attributes for us from the _Pub/Sub_ message (stored as the `event` object), and deserializes the body which is encoded.

Then, it pushes the data to the endpoint from the attribute and logs the `eventName` attribute.

Now that we know what our code will be doing, we can click the "Edit" button in the function toolbar, and then update our function.  We'll start with the package.json, and then the index.js file.  

>Note: Remember to update the "Function to Execute" field with the name of our exported node function.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/d9yn1i6pgprygjukjh8r.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ikgjpkyebjwtnh8tt9g6.png)

Once you're done, click the "Deploy" button to update our function.

> Note: you may have noticed that unlike in our AWS example, Google Cloud manages our "node_modules" dependencies for us when using node as our runtime.  This means we don't need to upload those dependencies in a zip like we had to with _Lambda_.

### Step Four: Test our Solution
We're going to use the same tool to test our Google Cloud backed webhooks as we did with AWS: [webhooks.site](https://webhook.site).

Head there now to generate a new test suite, and copy the endpoint URL from the page.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1gaknh09710na69gilco.png)

Next, we'll jump back to _Cloud Pub/Sub_ and open up our topic.

In the top header bar for our topic, click the "Publish Message" button to test our solution the same way we did with _SQS_ on AWS.

In the "Message Body", add whatever data you want to test with.  In my case, I'm testing a simulated JSON webhook.

Next, add an attribute called "endpoint", which we've told our function to look out for, and will contain the destination URL for our webhook event.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0c13iwhj77akiwaaazb8.png)

Click "Publish" to send our message, which will trigger our function and submit the request to our webhook.site endpoint.

After a few seconds, we should see our request in our webhook.site dashboard, confirming our backend functioning as expected.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9za2ed2csxxif65fhmg8.png)

Finally, we can review the results of our function's execution by navigating back to our function, and clicking "View Logs" from the top bar, which will bring us to Google Cloud's log viewer.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8b6ja4wbv91sllviurui.png)

> Note: Notice anything? Our function was successfully logged, but our log line says "Dispatched webhook for [Undefined]".  This is because we did not test our _Pub/Sub_ message with an "event_name" parameter, which our function uses to log. Oops!

### Conclusion
Similar to AWS, our final step is to connect our own application to this infrastructure we've just built.

Google provides a number of SDKs in popular languages to support the seamless integration of Google Cloud with your products.  Simply download the one that makes sense for you, connect it to your Google Cloud account, and start pushing messages to _Cloud Pub/Sub_.

Whether you prefer AWS or Google Cloud (or even Azure, Digital Ocean, or someone else), setting up your infrastructure to support webhooks is simple.  

_Extensibility_ should be considered an essential component in any software solution. The needs of your users will often evolve in ways that you won't anticipate, and trying to plan for everything is good way to end up overwhelmed and exhausted. 

In less than half an hour you can set up a quick implementation to start adding webhooks to your application's lifecycle and immediately start working with external solutions to your users' needs.

_Interested in learning more about my thoughts on **extensibility** and my other pillars of software design? Check out my take on [Pragmatic Software Design](https://dev.to/brandinchiu/pragmatic-software-design-a94)!_