<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Brandin Chiu</title>
    <description>The latest articles on DEV Community by Brandin Chiu (@brandinchiu).</description>
    <link>https://dev.to/brandinchiu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F312873%2F8d2057ba-275c-4d44-b135-87eb07ddad26.png</url>
      <title>DEV Community: Brandin Chiu</title>
      <link>https://dev.to/brandinchiu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brandinchiu"/>
    <language>en</language>
    <item>
      <title>Extensibilty through Webhooks Part 3: Google Cloud</title>
      <dc:creator>Brandin Chiu</dc:creator>
      <pubDate>Mon, 08 Jun 2020 12:56:02 +0000</pubDate>
      <link>https://dev.to/brandinchiu/extensibilty-through-webhooks-part-3-google-cloud-3dmm</link>
      <guid>https://dev.to/brandinchiu/extensibilty-through-webhooks-part-3-google-cloud-3dmm</guid>
      <description>&lt;p&gt;Now that we've had an opportunity to see howe we can implement webhooks using &lt;a href="https://dev.to/brandinchiu/extensibilty-through-webhooks-part-2-amazon-web-services-35hg"&gt;Amazon Web Services&lt;/a&gt;, 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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;The Google Cloud products we will be using for this demonstration will be: &lt;em&gt;&lt;a href="https://cloud.google.com/functions"&gt;Cloud Functions&lt;/a&gt;&lt;/em&gt;, and &lt;em&gt;&lt;a href="https://cloud.google.com/pubsub"&gt;Cloud Pub/Sub&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cloud Functions&lt;/em&gt; 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 &lt;em&gt;Cloud Functions&lt;/em&gt; supports Go, Java, and Python as well.&lt;/p&gt;

&lt;p&gt;We will be connecting our &lt;em&gt;Cloud Function&lt;/em&gt; to a &lt;em&gt;Cloud Pub/Sub topic&lt;/em&gt;, which will be how we invoke our function.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cloud Pub/Sub&lt;/em&gt; is a messaging service for Google Cloud, and functions in a similar way to Amazon's &lt;em&gt;Simple Queue Service&lt;/em&gt;, only with less focus on the queueing functionality (which we will not be needing anyway).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step One: Create a Project
&lt;/h3&gt;

&lt;p&gt;Google Cloud manages resources differently from Amazon Web Services.  In AWS, resources are defined globally on the account and segmented by regions.&lt;/p&gt;

&lt;p&gt;In Google Cloud, resources are managed in &lt;em&gt;projects&lt;/em&gt;.  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.&lt;/p&gt;

&lt;p&gt;That means, in order to start working with &lt;em&gt;Cloud Pub/Sub&lt;/em&gt; and &lt;em&gt;Cloud Functions&lt;/em&gt;, we first need to a project.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--klgKfQf9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nbn5orcnzw6pwan8k0uv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--klgKfQf9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nbn5orcnzw6pwan8k0uv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YNNzfZu8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5hyezrbyaygc0ii1jmhs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YNNzfZu8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5hyezrbyaygc0ii1jmhs.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're now ready to begin!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Two: Create the Pub/Sub Topic
&lt;/h3&gt;

&lt;p&gt;In order to trigger our webhooks to fire, we'll be making use of &lt;em&gt;Cloud Pub/Sub&lt;/em&gt; as a messaging bus.  Messages sent to &lt;em&gt;Pub/Sub&lt;/em&gt; are organized using "topics".&lt;/p&gt;

&lt;p&gt;Open up the side navigation in Google Cloud and launch &lt;em&gt;Pub/Sub&lt;/em&gt; (located under the "Big Data" header).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PRcUaj8c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sb815w8bzdpua8av8yv4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PRcUaj8c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sb815w8bzdpua8av8yv4.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;next, select "Create Topic" from the &lt;em&gt;Pub/Sub&lt;/em&gt; landing page to create our webhooks topic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TwAXf6eu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/18g8l8revhpvx09v82so.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TwAXf6eu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/18g8l8revhpvx09v82so.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give your topic a name, and leave the encryption options as the default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F1nn7atK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nm549wyc0rs0qezt0oyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F1nn7atK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nm549wyc0rs0qezt0oyh.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your topic will take a few seconds to be generated, and then you'll see the topic dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tjZLV3BJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jp2zdc8qyei5gjw0pm69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tjZLV3BJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jp2zdc8qyei5gjw0pm69.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: it's so common for &lt;em&gt;Pub/Sub&lt;/em&gt; to be connected to a &lt;em&gt;Cloud Function&lt;/em&gt; that there are helpful shortcuts built into the page near the top! &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step Three: Configuring our Cloud Function
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Open our side menu back up and navigate to &lt;em&gt;Cloud Functions&lt;/em&gt; (located under the "Compute" header).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--it6oZz3p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/c7xrn7aya8m5oq6tbfgm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--it6oZz3p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/c7xrn7aya8m5oq6tbfgm.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll be dropped on the empty list page for your project's &lt;em&gt;Cloud Functions&lt;/em&gt;, so let's create one now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cQet9t2K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zed9fxcosubc7ld3f3na.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cQet9t2K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zed9fxcosubc7ld3f3na.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Start by giving your function a name, and then leave the "Memory Allocated" as the default value of 256 MB.&lt;/p&gt;

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

&lt;p&gt;Leave the rest of the values as their defaults, and click "Create" at the bottom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--etBTOq2I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/u56ddx70ryz26gmbjao6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--etBTOq2I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/u56ddx70ryz26gmbjao6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xlxWWsxn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b5ga07iqdg1mfuee9r2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xlxWWsxn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b5ga07iqdg1mfuee9r2m.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A1J8008G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3s4lt79ul21tfxyjdufd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A1J8008G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3s4lt79ul21tfxyjdufd.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Just like with AWS, we'll use a very simple node script to push our requests to our webhook endpoints.&lt;/p&gt;

&lt;p&gt;Our first step will be to modify the package.json file.  The end result should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dispatch-webhooks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@google-cloud/pubsub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.18.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"node-fetch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.6.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We'll leave the &lt;em&gt;Pub/Sub&lt;/em&gt; dependency that's already there, and add our "node-fetch" package.&lt;/p&gt;

&lt;p&gt;Next, we'll update our source code using the following simple script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const fetch = require('node-fetch');

exports.dispatchWebhook = (event, context) =&amp;gt; {
  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) =&amp;gt; {
        console.log("Dispatched webhook for [" + eventName + "]: " + response.status);
    })
  ;
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This script doesn't actually do very much.  It pulls some event attributes for us from the &lt;em&gt;Pub/Sub&lt;/em&gt; message (stored as the &lt;code&gt;event&lt;/code&gt; object), and deserializes the body which is encoded.&lt;/p&gt;

&lt;p&gt;Then, it pushes the data to the endpoint from the attribute and logs the &lt;code&gt;eventName&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;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.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Remember to update the "Function to Execute" field with the name of our exported node function.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6DLct-Dp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d9yn1i6pgprygjukjh8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6DLct-Dp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d9yn1i6pgprygjukjh8r.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KY4fCLQB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ikgjpkyebjwtnh8tt9g6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KY4fCLQB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ikgjpkyebjwtnh8tt9g6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you're done, click the "Deploy" button to update our function.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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 &lt;em&gt;Lambda&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step Four: Test our Solution
&lt;/h3&gt;

&lt;p&gt;We're going to use the same tool to test our Google Cloud backed webhooks as we did with AWS: &lt;a href="https://webhook.site"&gt;webhooks.site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Head there now to generate a new test suite, and copy the endpoint URL from the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--No_EpYbY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1gaknh09710na69gilco.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--No_EpYbY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1gaknh09710na69gilco.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we'll jump back to &lt;em&gt;Cloud Pub/Sub&lt;/em&gt; and open up our topic.&lt;/p&gt;

&lt;p&gt;In the top header bar for our topic, click the "Publish Message" button to test our solution the same way we did with &lt;em&gt;SQS&lt;/em&gt; on AWS.&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KNfx6iyX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0c13iwhj77akiwaaazb8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KNfx6iyX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0c13iwhj77akiwaaazb8.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Publish" to send our message, which will trigger our function and submit the request to our webhook.site endpoint.&lt;/p&gt;

&lt;p&gt;After a few seconds, we should see our request in our webhook.site dashboard, confirming our backend functioning as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ttSchrFz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9za2ed2csxxif65fhmg8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ttSchrFz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9za2ed2csxxif65fhmg8.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z47k5sve--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8b6ja4wbv91sllviurui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z47k5sve--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8b6ja4wbv91sllviurui.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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 &lt;em&gt;Pub/Sub&lt;/em&gt; message with an "event_name" parameter, which our function uses to log. Oops!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Similar to AWS, our final step is to connect our own application to this infrastructure we've just built.&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;Cloud Pub/Sub&lt;/em&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Extensibility&lt;/em&gt; 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. &lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Interested in learning more about my thoughts on &lt;strong&gt;extensibility&lt;/strong&gt; and my other pillars of software design? Check out my take on &lt;a href="https://dev.to/brandinchiu/pragmatic-software-design-a94"&gt;Pragmatic Software Design&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>aws</category>
      <category>gcloud</category>
    </item>
    <item>
      <title>Extensibilty through Webhooks Part 2: Amazon Web Services</title>
      <dc:creator>Brandin Chiu</dc:creator>
      <pubDate>Mon, 08 Jun 2020 12:55:11 +0000</pubDate>
      <link>https://dev.to/brandinchiu/extensibilty-through-webhooks-part-2-amazon-web-services-35hg</link>
      <guid>https://dev.to/brandinchiu/extensibilty-through-webhooks-part-2-amazon-web-services-35hg</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/brandinchiu/extensibilty-through-webhooks-part-1-what-are-webhooks-1d0e"&gt;previous post&lt;/a&gt; we briefly discussed what webhooks are and why they might be helpful.  In this post, we will look at a step-by-step implementation of a cloud-hosted solution for dispatching webhooks for our application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: In order to proceed you will need an AWS account.  Luckily, all of the features within AWS we will be using are free within the limits we will be exploring.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  How it Works
&lt;/h3&gt;

&lt;p&gt;We will be making use of two AWS products in order to build our webhooks solution: &lt;em&gt;Simple Queue Service (SQS)&lt;/em&gt;, and &lt;em&gt;Lambda&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Lambda&lt;/em&gt; is a serverless execution platform for running code remotely.  This will be the logic component of our solution, written in node, and will be ultimately responsible for dispatching the webhook.  Our function will live inside &lt;em&gt;Lambda&lt;/em&gt;, and it will listen for events within the AWS ecosystem and then execute that function using parameters from that event.&lt;/p&gt;

&lt;p&gt;Our &lt;em&gt;Lambda&lt;/em&gt; function will be listening for an &lt;em&gt;SQS&lt;/em&gt; event, which will fire every time a new item is added to our &lt;em&gt;SQS&lt;/em&gt; queue.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Simple Queue Service&lt;/em&gt; is essentially a message bus for AWS and third party services.  We push items (called messages) into &lt;em&gt;SQS&lt;/em&gt;, which are then read by consumers who act on those messages.&lt;/p&gt;

&lt;p&gt;In our case, we will configure our &lt;em&gt;Lambda&lt;/em&gt; function to be our consumer, who will automatically read any message added to the queue and use it to submit our webhook request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step One: Configure the Queue
&lt;/h3&gt;

&lt;p&gt;Our first step is to create the queue where we will submit our webhook events.&lt;/p&gt;

&lt;p&gt;From your AWS homepage, find and open the SQS service.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: you can either search the page for the service by name, or use the search function, which supports common acronyms for AWS services, such as "SQS".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Assuming you have never worked with &lt;em&gt;SQS&lt;/em&gt; before, you should see the following screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DH3ZiYzx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vzf5z6yypdq4btvx11ct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DH3ZiYzx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vzf5z6yypdq4btvx11ct.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the "Get Started Now" button to be prompted to create your first queue, or create a new queue from the queue list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f-zbFzFc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7umoak8uepleavfctwdl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f-zbFzFc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7umoak8uepleavfctwdl.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;SQS&lt;/em&gt; supports two different kinds of queues: standard and FIFO.  The &lt;em&gt;SQS&lt;/em&gt; service provides high-availability and redundancy for your messages by mirroring them across hundreds of AWS servers.  This can sometimes mean that whenever you query AWS for a new set of messages, you may possibly receive the same message twice.  Normally, it's up to your application to correctly handle duplicates.  Additionally, in &lt;strong&gt;standard&lt;/strong&gt; queues, messages are not handled in-order.&lt;/p&gt;

&lt;p&gt;For these reasons, we will be using Amazon's "FIFO" (or first-in-first-out) queues.  Amazon will limit these queues to process the messages sent to them in order.  This also prevents duplicates from being entered into the queue.  You can learn more about the different queue types &lt;a href="https://aws.amazon.com/sqs/features/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Click the "Quick-Create Queue" button to create our FIFO queue.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are a number of configuration parameters that can be defined for how our queue should behave.  These are outside the scope of what we need to worry about at this point, but I encourage you to read more about them &lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-how-it-works.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You should see the queue list screen with our new queue!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--slexGqhc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7s4vcc8vwpm6asiql5ub.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--slexGqhc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7s4vcc8vwpm6asiql5ub.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Two: Configuring our Lambda Function
&lt;/h3&gt;

&lt;p&gt;Now that our queue is configured and ready to receive messages, we need to set up our &lt;em&gt;Lambda&lt;/em&gt; function and hook it up to the queue to act as our consumer.&lt;/p&gt;

&lt;p&gt;Return to the AWS homepage or click on the "Services" header and search for "Lambda".  You should see the Lambda screen like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gvbjlaEy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y44f5farpcua31joweli.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gvbjlaEy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y44f5farpcua31joweli.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the orange "Create Function" button in the top-right corner to get started creating a new function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nSKu9QNa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0d2784dmrrqjryrvm3z9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nSKu9QNa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0d2784dmrrqjryrvm3z9.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give your function a name and choose node as your runtime.&lt;/p&gt;

&lt;p&gt;Under "Permissions" select "Create a new role from AWS policy templates".  Give your new role a name, and then under "Policy templates" start typing "sqs" until you see the "Amazon SQS poller permissions" policy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Amazon and most other cloud providers use a system called &lt;a href="https://en.wikipedia.org/wiki/Identity_management"&gt;IAM&lt;/a&gt; to restrict permissions to invoke their services.  These rules are created as roles which are then attached to users and other services. In our case, to allow our &lt;em&gt;Lambda&lt;/em&gt; function to talk to &lt;em&gt;SQS&lt;/em&gt;, we need to create a new IAM role (or use an existing one) with the correct permissions and attach it to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Click "Create function" in the bottom right when ready.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fo3rKOAx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b0re1drpxknl56xn12gk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fo3rKOAx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b0re1drpxknl56xn12gk.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, click the "+ Add Trigger" button to link our &lt;em&gt;Lambda&lt;/em&gt; to our &lt;em&gt;SQS&lt;/em&gt; queue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c85MhrcC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1y4wkg0de08fltpmn6rf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c85MhrcC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1y4wkg0de08fltpmn6rf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set "SQS" as the service, select the queue we created in step one, and set the batch size to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Click "Add" when done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DYU4W_D6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5st9xf03s4gmj6cpmk5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DYU4W_D6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5st9xf03s4gmj6cpmk5o.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, select our &lt;em&gt;Lambda&lt;/em&gt; functions name from the center of the diagram and scroll down to the "Function code" section.&lt;/p&gt;

&lt;p&gt;Our code is going to submit the webhook request using the &lt;em&gt;node-fetch&lt;/em&gt; package, which means we'll need to add it as a dependency.  In order to properly bundle the needed &lt;code&gt;node_modules&lt;/code&gt;, I've included a zip file below for you to upload to &lt;em&gt;Lambda&lt;/em&gt; by "Code entry type" to "Upload a zip file".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://misfitpixel-public.s3.amazonaws.com/lambda.zip"&gt;https://misfitpixel-public.s3.amazonaws.com/lambda.zip&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lL80TcGH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wb5x1ixm6px98wnm5b4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lL80TcGH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wb5x1ixm6px98wnm5b4o.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The zip file includes the necessary dependencies and a single JS file, index.js, which includes our function code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./package.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messageAttributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messageAttributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Lambda provides data from our incoming &lt;em&gt;SQS&lt;/em&gt; messages from an &lt;code&gt;event&lt;/code&gt; object it passes to our function which we then use to submit the request.  Ultimately, all this function does is read data from the incoming &lt;em&gt;SQS&lt;/em&gt; message, and submit a new request to a provided URL from that data.  It submits those requests as a promise which we do not bother resolving, since we don't really care how the target endpoint responds to our request.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: in a production example, we would probably want to do some level of error handling in order to provide some kind of retry service for failed hooks or better error logging to the client.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once your code is uploaded and you're ready, click the orange "Save" button at the top right.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Three: Test our Solution
&lt;/h3&gt;

&lt;p&gt;Good news for us, the &lt;em&gt;SQS&lt;/em&gt; dashboard provides a nice, clean way to push messages directly to our queue without needing to install or build any other software.  This gives us a simple mechanism to simulate our backend infrastructure.&lt;/p&gt;

&lt;p&gt;We're going to need a destination endpoint to test that our webhook was delivered, so we'll use the service provided by &lt;a href="https://webhook.site"&gt;https://webhook.site&lt;/a&gt;.  This will give us a temporary endpoint to play with, and an interface to verify our requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LpVdzpYp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2mxplagtzde5rqdwe74u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LpVdzpYp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2mxplagtzde5rqdwe74u.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy your unique URL from the page, and then navigate back to the SQS dashboard inside AWS.  If you right-click on the name of your queue, a context menu will pop up and give you the option to "Send a message".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uzfqk8q3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9u49s9mx9ku7iwrci2th.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uzfqk8q3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9u49s9mx9ku7iwrci2th.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;SQS&lt;/em&gt; messages consist of a combination of a string body, and key-value pairs called "attributes".&lt;/p&gt;

&lt;p&gt;Our solution in particular makes use of both.  Our HTTP request body is stored in the message body, and then we store the HTTP method name, and the URL itself as an attribute.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: we've included the HTTP method as an attribute as an example. However, your webhook requests should really always dispatched as a POST request.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start by adding a message to your queue with whatever body you want.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sPT1lEhe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/by194zqusvq88vost4sd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sPT1lEhe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/by194zqusvq88vost4sd.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll also notice two other fields underneath our body.  These requirements are specific to FIFO queues.  For our tests, you can really enter any text data into these fields.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the Message Group ID is an optional sub-collection within your FIFO queue.  Messages with the same Group ID will always be managed in FIFO order.  If you have messages where the processing order does not matter, you can give them different (or random) Group IDs.&lt;/p&gt;

&lt;p&gt;Note: the Message Deduplication ID is a unique identifier for a particular message. No two items in a FIFO queue can share a deduplication ID.  If you push a message to the queue with the same ID as one already in it, your new upload will be ignored.  It's common practice to use an MD5 hash of your message contents as the deduplication ID to unique identify messages (which SQS can do for you automatically through your queue configuration).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, add the attributes we discussed above: your HTTP &lt;code&gt;method&lt;/code&gt;, which will be &lt;code&gt;POST&lt;/code&gt;, and your &lt;code&gt;url&lt;/code&gt;, which will be your unique webhook.site endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YQVjBv-P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5pzisy7e29copgw6lok4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YQVjBv-P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5pzisy7e29copgw6lok4.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simply enter the attribute name, followed by it's value, and then click the "Add Attribute" button to add it to the message.&lt;/p&gt;

&lt;p&gt;Once you're done, click "Send Message", then close the confirmation dialog.&lt;/p&gt;

&lt;p&gt;Your message will be pushed to the queue, but you may notice that after you return to the queue dashboard, AWS will list there being no messages pending in your queue.&lt;/p&gt;

&lt;p&gt;This is due to the connection of our &lt;em&gt;Lambda&lt;/em&gt; to our queue.  As soon as our message hits the queue, AWS sends an event to &lt;em&gt;Lambda&lt;/em&gt;, who then consumes and deletes the message with our &lt;em&gt;Lambda&lt;/em&gt; function.&lt;/p&gt;

&lt;p&gt;If we navigate back to our webhook.site dashboard, we should see our request with the message body:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O5yqWAhv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1687uapdqibul16092sp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O5yqWAhv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1687uapdqibul16092sp.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our solution works! We can do one last step of confirmation by checking the &lt;em&gt;Lambda&lt;/em&gt; logs.&lt;/p&gt;

&lt;p&gt;Navigate back to your &lt;em&gt;Lambda&lt;/em&gt; function, and then click the "Monitoring" tab.  At the top right of that page, you'll find a "View logs in CloudWatch" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y54XkVeE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/skxkj5igiv7f6lyophsf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y54XkVeE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/skxkj5igiv7f6lyophsf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking on that button will open the cloud logs where you can view the output of your &lt;em&gt;Lambda&lt;/em&gt; function's execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--INHARDZO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hw6p61ldfu0747uk65jz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--INHARDZO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hw6p61ldfu0747uk65jz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: our logs here only contain default logging information from &lt;em&gt;Lambda&lt;/em&gt;.  If we were to add our own &lt;code&gt;console.log()&lt;/code&gt; statements to our function, those outputs would be captured here as well.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;We've completed the backend cloud architecture of our webhook solution and verified that it is working.  We've established a queue to hold our outgoing webhooks, and linked it to a serverless function via &lt;em&gt;Lambda&lt;/em&gt; to consume those messages and dispatch the webhook.&lt;/p&gt;

&lt;p&gt;At this point, the last step is to connect it directly to our own application.&lt;/p&gt;

&lt;p&gt;Amazon provides SDKs for most major languages, which you can quickly add to your application to start interacting with AWS services.  Doing this requires creating a "programmatic" user account account ID and secret to authenticate with the SDK, and then using it to push messages to &lt;em&gt;SQS&lt;/em&gt;, which will then automatically be picked up and handled by &lt;em&gt;Lambda&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Ultimately, your application will also require the ability to register an endpoint with an event in your application.&lt;/p&gt;

&lt;p&gt;Hopefully this tutorial gave you a quick glimpse of how you can use Amazon Web Services to quickly set up a backend to handle dispatching webhooks for your application!&lt;/p&gt;

&lt;p&gt;The next article will look to do the same thing over again, but using Google Cloud instead!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>aws</category>
    </item>
    <item>
      <title>Extensibilty through Webhooks Part 1: What are webhooks?</title>
      <dc:creator>Brandin Chiu</dc:creator>
      <pubDate>Mon, 08 Jun 2020 12:54:33 +0000</pubDate>
      <link>https://dev.to/brandinchiu/extensibilty-through-webhooks-part-1-what-are-webhooks-1d0e</link>
      <guid>https://dev.to/brandinchiu/extensibilty-through-webhooks-part-1-what-are-webhooks-1d0e</guid>
      <description>&lt;p&gt;At the center of my discussion on &lt;a href="https://dev.to/brandinchiu/pragmatic-software-design-a94"&gt;Pragmatic Software Design&lt;/a&gt; I discussed the four pillars of what I consider to be successful software solutions.&lt;/p&gt;

&lt;p&gt;This post revolves around one of those pillars in particular: &lt;em&gt;&lt;strong&gt;extensibility&lt;/strong&gt;&lt;/em&gt;, and how we can make our solution as easy to work with as possible for other developers and/or services.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are webhooks?
&lt;/h3&gt;

&lt;p&gt;A &lt;em&gt;webhook&lt;/em&gt; is an automated submission of data over HTTP as a response to some lifecycle event of importance.  Users (typically third-party developers) can register to "listen" to these events by providing a URL, which the service will use to submit a request whenever one of those events fires.&lt;/p&gt;

&lt;p&gt;Now, why is this useful?  Before webhooks, retrieving data out of another system often required some kind of polling.  However, polling is inherently inefficient as it generates significantly more network traffic than needed as you constantly ask the service for data.&lt;/p&gt;

&lt;p&gt;Webhooks give us the ability to restrict network usage to only the bare minimum needed to obtain the data we're looking for.&lt;/p&gt;

&lt;p&gt;As an example, imagine you are looking to integrate with a service that manages users, with another that sends them emails.  Service A is responsible for registering and maintaining your primary source of users.  Service B maintains a separate list of users that have opted-in to receive marketing, and periodically sends them deals on new products.&lt;/p&gt;

&lt;p&gt;If Service A supports webhooks for new user registrations, then it can automatically notify Service B each time a new user registers.  Service B can then ingest that data in near real-time, confirm that the user has given permission to receive marketing emails, and then add them to the list of users in Service B.&lt;/p&gt;

&lt;p&gt;The true power of webhooks, however, isn't any one specific use-case, but is instead the flexibility for services to integrate with each other in a variety of ways that the initial developers of that service do not expect.  It allows for more efficient use of a team's resources by allowing them to focus on their own service, and instead provide a flexible bridge for others to extend the functionality of that service naturally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Webhooks to your service
&lt;/h3&gt;

&lt;p&gt;Now that we have a handle on what webhooks are and why they might be valuable for us to implement, we're going to discuss a bare-bones and quick way to add webhooks to our own application using Amazon Web Services or Google Cloud.&lt;/p&gt;

&lt;p&gt;We want to use a cloud service as the backend for our webhooks because we want to decouple the delivery and execution of our events from our actual application.  We want the execution of our webhooks to occur independently from our application -- we're registering endpoint URLs that we do not control and therefore we want any potential errors or issues arising from our webhook requests to be processed externally.&lt;/p&gt;

&lt;p&gt;If you're ready, proceed ahead to follow along with the tutorial.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>aws</category>
      <category>gcloud</category>
    </item>
    <item>
      <title>Pragmatic Software Design</title>
      <dc:creator>Brandin Chiu</dc:creator>
      <pubDate>Fri, 22 May 2020 11:26:41 +0000</pubDate>
      <link>https://dev.to/brandinchiu/pragmatic-software-design-a94</link>
      <guid>https://dev.to/brandinchiu/pragmatic-software-design-a94</guid>
      <description>&lt;p&gt;As developers, it's easy to get lost at sea with all the patterns, techniques, tools, services, and paradigms we're expected to somehow know.&lt;/p&gt;

&lt;p&gt;This is especially difficult when needing to design software systems from scratch.  Which language should I use? What patterns should I follow? Which dependencies should I rely on? The list of questions never ends, and it can be paralyzing to think about how a perceived wrong decision from the start could come back to haunt you later.&lt;/p&gt;

&lt;p&gt;I've experienced these fears at one time or another for most of my career, and it's really only been recently that I've adopted a change in perspective that has sincerely helped reshape my attitude and approach to software design.&lt;/p&gt;

&lt;p&gt;This idea, distilled to its most basic foundation is &lt;em&gt;pragmatism&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focusing on Solutions
&lt;/h3&gt;

&lt;p&gt;At its heart, programming and development is about &lt;strong&gt;solving problems&lt;/strong&gt;.  I've quickly discovered that the fundamental characteristics of these problems are almost always the same, regardless of the original scale or scope of the project.&lt;/p&gt;

&lt;p&gt;For a business, the most important thing is presenting a solution to your users.  In the real world, that solution doesn't need to be perfect, and it likely never will be.&lt;/p&gt;

&lt;p&gt;We're both fortunate and unfortunate as programmers that there are often many different ways to resolve a particular problem.  Being pragmatic means picking a solution and moving forward with it, without worrying about whether that solution will be viable or successful forever.&lt;/p&gt;

&lt;p&gt;At the end of the day, it's the solution that matters.  Your users and your stakeholders are very rarely going to care if you've reached the optimal resource usage or if you've solved for &lt;a href="https://en.wikipedia.org/wiki/Big_O_notation"&gt;Big O Notation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Planning Ahead
&lt;/h3&gt;

&lt;p&gt;Now, that isn't to say that efficiency isn't important, and being pragmatic is not a blank-cheque to write sloppy code.&lt;/p&gt;

&lt;p&gt;But it is important to keep in mind how our solutions are actually going to run in the wild.  For most of us, the efficiency ceiling of our solutions will be defined by our product and not by how our users actually use that product.&lt;/p&gt;

&lt;p&gt;When we're designing software, we should be planning at a higher level for how our solution might grow, and to ultimately make it as flexible as possible.  At the end of the day, our biggest issue shouldn't be whether our solution will live on Apache, nginx, nodejs, or maybe something else -- it should be how it might be adaptable to work on any of them later.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pillars of Software Design
&lt;/h3&gt;

&lt;p&gt;Good software design requires maintaining a balance between reasonable planning and actually getting things done.  A more pragmatic and flexible approach not only helps keep us from being boxed into corners with specific toolings, but also makes us more Agile.&lt;/p&gt;

&lt;p&gt;Thus, instead of worrying about granular-level problems like hosting, caching, etc, we take a step back and plan from a higher level.  For me, that means breaking down each of my solutions in a way that helps them meet the following core objectives, or "pillars":&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Maintainability&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
It goes without saying that for most of our careers we will be spent working on software someone else has written.  The number of unique opportunities each of us will have to design a solution from nothing will be few and far between.&lt;/p&gt;

&lt;p&gt;This is why designing that solution in a way that is &lt;em&gt;maintainable&lt;/em&gt; is so important.  Our goal here is to make it as simple as possible for developers to understand our code.  There a number of easy and simple ways to do this that many of you will likely already do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;descriptive and useful inline documentation&lt;/li&gt;
&lt;li&gt;consistent application of style guidelines and linting&lt;/li&gt;
&lt;li&gt;common-sense naming rules for variables, methods, and classes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next step is to actually look at the code we write, and how it could be improved to make it naturally easier to understand.  Avoiding the &lt;a href="https://en.wikipedia.org/wiki/Code_golf"&gt;"Code Golf"&lt;/a&gt; trap is definitely valuable and contributes a lot to making our code easier to read.&lt;/p&gt;

&lt;p&gt;And once we've mastered these simple things to look out for, we can dive into some more complex ones like better managing our application's &lt;a href="https://en.wikipedia.org/wiki/Cyclomatic_complexity"&gt;Cyclomatic Complexity&lt;/a&gt; so that it's easier to follow our execution path from the code alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Scalability&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
How our solution scales should always be on our mind.  How does our solution hold up with 1000 users? What about 100000? These are the kinds of questions we should be asking ourselves as we design our products.&lt;/p&gt;

&lt;p&gt;The great thing about thinking about these questions early is that it means we can plan for &lt;em&gt;how&lt;/em&gt; we might solve these problems later, without actually needing to implement things immediately.&lt;/p&gt;

&lt;p&gt;The more important of these internal conversations is not what tool we will use to implement our scaling, but instead is and understanding where our potential scaling vulnerabilities are.&lt;/p&gt;

&lt;p&gt;Understanding which aspects of your application scale vertically vs horizontally, or which processes will benefit from caching, is significantly more valuable that the implementation of these steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Stability&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Building a stable solution is always a goal for a developer.  However, in my experience, the most stable solutions are the ones that are &lt;em&gt;proactive&lt;/em&gt; rather than reactive.&lt;/p&gt;

&lt;p&gt;Stability can often be achieved as a byproduct of already building &lt;em&gt;maintainable&lt;/em&gt; and &lt;em&gt;scalable&lt;/em&gt; solution.  And this makes sense since stability is really just a function of how long it takes to fix a problem (maintainability), and how well it handles traffic (scalability) over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Extensibility&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
The most successful solutions that we hear about in recent years have been those that tend to be developer-friendly.  These huge enterprises often have dedicated developer resources where they invite others to build custom solutions for the problems &lt;strong&gt;their&lt;/strong&gt; users face.&lt;/p&gt;

&lt;p&gt;This is the difference between being a product, and being a &lt;em&gt;platform&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And while not every solution, app, or service will have need to mobilize a network of developers to extend their platform, it should always be considered as a way to broaden your solution's reach.&lt;/p&gt;

&lt;p&gt;Common and easy ways to promote extensibility include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;making either your entire or a portion of your API public&lt;/li&gt;
&lt;li&gt;implementing webhooks for specific important lifecycle events&lt;/li&gt;
&lt;li&gt;authenticating through OAuth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whenever possible, at least some level of extensibility should be built into your solution in order to give you room to organically grow with your users.  It will never be possible to fully anticipate every need of every user -- leaving room for external developers to work with you to solve some of those unique problems allows you to focus on larger-scale goals while still responding quickly to the ever-changing requirements of your users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Many otherwise good (or even great) ideas have died on the page because they were never given the opportunity to live.  Getting stuck on the minutia of things like "should I use a factory pattern?", or "will this be deployed on Kubernetes/containers?" can often derail our progress.&lt;/p&gt;

&lt;p&gt;If we adopt a more pragmatic approach that focuses more on the solution itself, and not the pieces underneath.  Instead, we take a much higher-level view of the goals we must accomplish in order to design an effective solution.  In the end, it must be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maintainable&lt;/li&gt;
&lt;li&gt;scalable&lt;/li&gt;
&lt;li&gt;stable&lt;/li&gt;
&lt;li&gt;extensible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once we've addressed these goals, we can look to fill in the blanks as we go, rather than front-loading all that responsibility at the start.  &lt;/p&gt;

&lt;p&gt;Inherently, this mindset should allow us to develop more flexible software, giving us the freedom to make adjustments quickly and efficiently to pivot as needed to keep our solution in scope of our pillars.&lt;/p&gt;

&lt;p&gt;I'd love to hear from you! Have you ever let a personal project die because you got overwhelmed by having to design the perfect piece of software the first time?  What strategies do you use when trying to solve this problem?&lt;/p&gt;

&lt;p&gt;Let me know!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;image courtesy of Unsplash&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
