<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[#100DaysOfCloud]]></title><description><![CDATA[#100DaysOfCloud]]></description><link>https://blog.100daysofcloud.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1596814625985/ozZ-yCtee.png</url><title>#100DaysOfCloud</title><link>https://blog.100daysofcloud.com</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 15 Oct 2026 07:12:31 GMT</lastBuildDate><atom:link href="https://blog.100daysofcloud.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[#100DaysOfCloud Twitter Spaces! 100 Cloud Talks!]]></title><description><![CDATA[🚧 This page is a Work In Progress.

Here is a list of upcoming Twitter Spaces. The plan is to do 100 Twitter Spaces because why not?.
FAQs

How are talks structured?
Are these Twitter Spaces for beginners or experts?
How long are these Twitter Space...]]></description><link>https://blog.100daysofcloud.com/100daysofcloud-twitter-spaces-100-cloud-talks</link><guid isPermaLink="true">https://blog.100daysofcloud.com/100daysofcloud-twitter-spaces-100-cloud-talks</guid><category><![CDATA[Cloud]]></category><dc:creator><![CDATA[Andrew Brown]]></dc:creator><pubDate>Fri, 15 Oct 2021 14:19:02 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>🚧 This page is a Work In Progress.</p>
</blockquote>
<p>Here is a list of upcoming Twitter Spaces. The plan is to do 100 Twitter Spaces because why not?.</p>
<h2 id="faqs">FAQs</h2>
<ul>
<li>How are talks structured?</li>
<li>Are these Twitter Spaces for beginners or experts?</li>
<li>How long are these Twitter Spaces?</li>
<li>What day do the Twitter Spaces occur?</li>
<li>What time of day are these Twitter Spaces?</li>
<li>Can anyone who attend speak? Are listeners allowed to ask questions?</li>
<li>I want to a speaker but I was never asked, can I request to be a speaker?</li>
<li>I missed the Twitter Space, are these recorded and where can I watch them?</li>
<li>Will you ever do the same topic twice?</li>
<li>What are the rules? Is their a CoC?</li>
</ul>
<h3 id="how-are-talks-structured">How are talks structured?</h3>
<p>Andrew the host will drive the conversation by asking questions. Speakers will reply. </p>
<p>There are suggestion questions, but this is an organic conversation, and we're hoping for attendees questions but most attendees like to listen.</p>
<p>Andrew can interrupt speakers at anytime</p>
<p>Its encouraged for guest speakers to raise their hand to queue for reply</p>
<p>For guest speakers watch for raised hands or allow for artificial pauses to allow another guest speaker to get a turn.</p>
<h3 id="are-these-twitter-spaces-for-beginners-or-experts">Are these Twitter Spaces for beginners or experts?</h3>
<p>#100DaysOfCloud community is focused on beginners. Our Twitter Spaces will contain experts but we are looking to answer fundamental knowledge across a wide range of topics.</p>
<h3 id="how-long-are-these-twitter-spaces">How long are these Twitter Spaces?</h3>
<p>~45 mins (30 minutes to 1 hour).</p>
<h3 id="what-day-do-the-twitter-spaces-occur">What day do the Twitter Spaces occur?</h3>
<p>Since we are planning 100 Twitter Spaces, they will be frequently. There is no set schedule eg. every Wednesday. We will be populating a public calendar for anyone to access, and you can get a reminder via Twitter since we'll post them a bit of ahead of time.</p>
<h3 id="what-time-of-day-are-these-twitter-spaces">What time of day are these Twitter Spaces?</h3>
<p>The time of day will vary. We have three possible slots for a talk.
The reason is because some of the speakers live in different time-zones and we want to cycle our time of day to ensure people in different parts of the world have the opportunity to attend. This doesn't mean a talk will be repeated multiple times, but that our talks will all over the place.</p>
<h4 id="morning-talk-1000am-et">Morning Talk (10:00am ET)</h4>
<p>other time zones for references</p>
<ul>
<li>7:00am PT</li>
<li>3:00pm  BST</li>
<li>7:30PM IST</li>
</ul>
<h4 id="afternoon-talk-300pm-et">Afternoon Talk (3:00pm ET)</h4>
<p>other time zones for reference</p>
<ul>
<li>Noon PST</li>
<li>8:00pm BST</li>
<li>12:30am IST</li>
</ul>
<h4 id="night-talk-9pm-et">Night Talk (9pm ET)</h4>
<p>other time zones for reference</p>
<ul>
<li>6pm PT</li>
<li>2:00am BST</li>
<li>6:30am IST</li>
</ul>
<h4 id="can-anyone-who-attend-speak-are-listeners-allowed-to-ask-questions">Can anyone who attend speak? Are listeners allowed to ask questions?</h4>
<p>The Twitter Space is a panel of approved speakers between 1 and 3 (4 if you include the host). Listeners can request to speak to ask a question, and must raise their hand and request to speak. After they ask their question they are put back to being a speaker.</p>
<p>If there is a known expert for the topic in question that shows up, and the panel is small, we may promote them to a speaker for the rest of the panel.</p>
<h4 id="i-want-to-a-speaker-but-i-was-never-asked-can-i-request-to-be-a-speaker">I want to a speaker but I was never asked, can I request to be a speaker?</h4>
<p>Direct Message <a target="_blank" href="https://twitter.com/andrewbrown">Andrew Brown on Twitter</a>. </p>
<h4 id="i-missed-the-twitter-space-are-these-recorded-and-where-can-i-watch-them">I missed the Twitter Space, are these recorded and where can I watch them?</h4>
<p>The recordings will be saved, but we are uncertain of the effort or technical challenges to save and upload them all, and will be polling the guest speakers. For the time being, if you want to hear a talk you need to attend in real-time.</p>
<h4 id="will-you-ever-do-the-same-topic-twice">Will you ever do the same topic twice?</h4>
<p>If the demand is their by speakers or the community in general we can repeat a topic so new people have an opportunity to participate in speaking and listening. </p>
<h4 id="what-are-the-rules-is-their-a-coc">What are the rules? Is their a CoC?</h4>
<p>100DaysOfCloud has a Code of Conduct. Generally just be respectful of other technologies. Try to limit swearing but aren't going to kick a speaker for doing so. Try not to be too devise or keep driving a point after its been made. Try and use gender-neutral language to the best of your ability.  </p>
<h2 id="list-of-planned-twitter-spaces">📋 List of Planned Twitter Spaces</h2>
<p>This is a list of planned twitter spaces. The order does not represent the order in which they will happen.  The questions listed are just suggestions and do not reflect the actual content of the talks. Until they are put on the calendar, the guest speakers and topic details can change.</p>
<p>If you want to see a Cloud talk that is not listed, please suggest so we can get 100 ideas! (42/100)</p>
<ol>
<li>What can we do with Minor Clouds?</li>
<li>Infrastructure as Code</li>
<li>CI/CD Pipelinees</li>
<li>Serverless Platform as a Service</li>
<li>Web3 vs Cloud</li>
<li>Cloud Development Environments</li>
<li>Cloud Security for Beginners</li>
<li>Azure for Beginners</li>
<li>GCP for Beginners</li>
<li>AWS for Beginners</li>
<li>DevOps for Beginners
1.K8 for Beginners</li>
<li>Containers for Beginners</li>
<li>Serverless for Beginners</li>
<li>Cloud Networking for Beginners</li>
<li>What can we do with Cloud Platforms?</li>
<li>Linux/UNIX knowledge for Cloud</li>
<li>Programming Skills needed for Cloud</li>
<li>Cloud Certifications</li>
<li>Cloud Roles
1.Burning Platforms and Digital Transformation</li>
<li>Cloud Cost Economics</li>
<li>PublicSector for Cloud </li>
<li>Cloud Governance</li>
<li>Hybrid Cloud </li>
<li>Virtualization and Cloud</li>
<li>Cloud Motiviated</li>
<li>Cloud for Newcomers</li>
<li>Cloud for Latecomers</li>
<li>Cloud for Notechers</li>
<li>Cloud Accessiblity </li>
<li>Identity for Cloud</li>
<li>Cloud Conferences</li>
<li>MLOps for Beginners</li>
<li>Cloud Data For Beginners</li>
<li>Learning in the Open</li>
<li>OpenSource for Cloud</li>
<li>Cloud Fundamentals</li>
<li>Cloud Resumes</li>
<li>Cloud Recruiters</li>
<li>Cloud Enterprise</li>
<li>Multi-Cloud </li>
<li>Climbing The Company Ladder</li>
<li>Cloud Projects</li>
</ol>
<h3 id="what-can-we-do-with-minor-clouds">What can we do with Minor Clouds?</h3>
<p>Everybody knows about AWS, Azure and GCP but what about the minor clouds? eg Vultr, Linode and Digital Ocean?</p>
<p>I am talking with Vlad Ionescu and Sathyajith Bhat on Twitter Spaces about our experiences where these minor cloud providers and their fit for your cloud workloads.</p>
<h4 id="questions">Questions</h4>
<ol>
<li>We know of Major providers eg. Azure, AWS and GCP. There are also many emerging or existing cloud service providers with few services around a core offering.  Should these providers be considered their minor cloud service providers, or are they the cloud platforms? </li>
<li>How many Minor Cloud Service Providers (mCSPs) currently exist? </li>
<li>Do they a specific edge or specialization that can be met or are they simply cost-effective with few services and a friendlier UI?</li>
<li>What can't you do on a Major Cloud Service Provider (MCSP) vs a Minor Cloud Service Provider? (mCSP)</li>
<li>What can't you do on a mCSP that you can do on a MCSP?</li>
<li>How hard is it to migrate from a mCSP to a CSP? Suppose you envision massive scale or major regulatory compliance in the future roadmap. Is it worth the initial agility on mCSP, or should we just be using a CSP at the start?</li>
<li>Do mCSP even have IaC? </li>
</ol>
<h3 id="cloud-networking-for-beginners">Cloud Networking for Beginners</h3>
<h4 id="questions">Questions</h4>
<ol>
<li>There is IT Networking and Cloud Networking. Are they the same thing, or are they different skill sets?</li>
<li>Is Cloud Networking a common obstacle to adopting the cloud for developers or companies in general?</li>
<li>Do you need to hold a Cisco Certification eg. Cisco Certified Network Associate (CCNA)?</li>
<li>If someone that holds a CCNA or has a background in IT Networking will be super-powered on Cloud Networking, will it be so different their skills will not translate?</li>
<li>Does Cloud Networking abstract away networking to the point you don't need specialized IT Networkers?</li>
<li>How little networking can I know to work in the cloud?</li>
<li>How do I learn cloud networking and how long is it going to take?</li>
<li>What is the career path for someone with cloud networking skills? Is their specialization or is it just table stakes?</li>
</ol>
<h3 id="infrastructure-as-code">Infrastructure as Code</h3>
<ul>
<li>What is IaC?</li>
<li>What IaC tools exists?</li>
<li>Who is leader for IaC tooling?</li>
<li>How hard it is to learn IaC?</li>
<li>What kind of workflows does IaC bring? eg. GitOps, Infrastructure Lifecycle</li>
</ul>
<h3 id="cicd-pipelinees">CI/CD Pipelinees</h3>
<ul>
<li>What is CI/CD?</li>
<li>What are self-hosted options?</li>
<li>What are the free-ish options?</li>
<li>What are the paid options?</li>
</ul>
<h3 id="serverless-platform-as-a-service-spas">Serverless Platform as a Service (SPaS)</h3>
<ul>
<li>What are these PaaS built around an opinionated serverless frameworks?<ul>
<li>Serverless Cloud</li>
<li>AWS Amplify</li>
<li>Supabase</li>
<li>Firebase</li>
</ul>
</li>
</ul>
<h3 id="web3-vs-cloud">Web3 vs Cloud</h3>
<ul>
<li>What is Web3?</li>
<li>Does Web3 use the cloud?</li>
<li>Is Blockchain on AWS centralized? </li>
<li>Will Web3 replace Cloud?</li>
<li>Does Web3 and Cloud compliment or complete? </li>
<li>Will the future be a centralized Web3 owned by CSPs and Tech Companies or a Web3 owned by the community and Cloud adopts tho that?</li>
</ul>
<h3 id="cloud-development-environments">Cloud Development Environments</h3>
<p>eg. Gitpods, Codespaces, Cloud9</p>
<ul>
<li>What is the cost to run a cloud development environment?</li>
<li>What "cant" I do with a cloud  development environment?</li>
<li>Which cloud development environment is best for collaboration?</li>
<li>What happens if my internet goes down?</li>
<li>Is it possible to adopt cloud development environment for my entire team or is this for individual?</li>
<li>Is VSCode the only option for Cloud Developer Environments?</li>
</ul>
<h3 id="cloud-security-for-beginners">Cloud Security for Beginners</h3>
<ul>
<li>Do I have to apprentice to get into Cloud Security?</li>
<li>Do I have to complete expensive certifications to get into Cloud Security?</li>
<li>So many initialism and security tools, Which ones do I need to know?</li>
<li>Is there a demand for Cloud Security Engineer or Security roles?  </li>
<li>How do I get deeper into the Cloud Security community?</li>
</ul>
<h3 id="azure-for-beginners">Azure for Beginners</h3>
<ul>
<li>What learning resources should I use?</li>
<li>Should I get certified or not?</li>
<li>If so what should my certification path look like?</li>
<li>What are essentials thing I should learn to get a job?</li>
<li>How do I get deeper into the Azure community?</li>
</ul>
<h3 id="gcp-for-beginners">GCP for Beginners</h3>
<ul>
<li>What learning resources should I use?</li>
<li>Should I get certified or not?</li>
<li>If so what should my certification path look like?</li>
<li>What are essentials thing I should learn to get a job?</li>
<li><p>How do I get deeper into the Google Cloud community?</p>
</li>
<li><p>How much does it cost to use to GCP?</p>
</li>
<li>IS GCP good as a first-cloud or should I start with something simpler? </li>
<li>Is it worth it to get Google Cloud Certified?</li>
<li>If so what should my certification path look like?</li>
<li>Does GCP excel very well at specific types of cloud workloads?</li>
<li>Are there specific industeries that specifically choose GCP over other Cloud Service Providers? eg. Health, AI/ML?</li>
<li>What is GCPs approach to the cloud, how does GCP think about cloud?</li>
<li>GCP has a total of count of cloud services around 100 which half of its competitors, does this equate to a reduce service offering, a stronger focus around common workloads excluding exotic workloads or just the way GCP organized or counts something as a "service" eg. AWS will promote features into services inflating their service count offering</li>
<li>What learning resources should I use?</li>
<li>Does AWS have any Google Cloud Developer communities eg. AWS User Groups, Azure User Groups.</li>
<li>Is GCP easier to use than other Cloud Service Providers?</li>
<li>How is GCP tackling sustainability? </li>
</ul>
<h3 id="aws-for-beginners">AWS for Beginners</h3>
<ul>
<li>What learning resources should I use?</li>
<li>Should I get certified or not?</li>
<li>If so what should my certification path look like?</li>
<li>What are essentials thing I should learn to get a job?</li>
<li>How do I get deeper into the AWS  community?</li>
</ul>
<h3 id="devops-for-beginners">DevOps for Beginners</h3>
<ul>
<li>What is DevOps?</li>
<li>Can I learn DevOps on my own or the only way in working at a company getting experience?</li>
<li>Do I need to know networking?</li>
<li>Do I need to know programming?</li>
<li>Do I have be on call and wake in the middle of the night?</li>
<li>Do I get to be in a datacenter and get a cool badge going through multiple security stages?</li>
<li>How much security do I need to know?</li>
</ul>
<h3 id="k8-for-beginners">K8 for Beginners</h3>
<ul>
<li>Should I learn K8?</li>
<li>Why is K8 popular?</li>
<li>Is K8 hard to learn?</li>
<li>Where should I host by K8 cluster?</li>
<li>When should you NOT use K8</li>
<li>What learning resources would be good for K8?</li>
</ul>
<h3 id="containers-for-beginners">Containers for Beginners</h3>
<ul>
<li>What kind of Container runtimes exist?</li>
<li>How much harder are containers vs VMs?</li>
<li>Docker vs Podman vs K8</li>
<li>Where can or should I run my containers?</li>
<li>Why not just use serverless instead of containers?</li>
<li>What tools make easier to build and CI/CD containers?</li>
<li>Where should I store my containers?</li>
</ul>
<h3 id="serverless-for-beginners">Serverless for Beginners</h3>
<ul>
<li>What is Serverless? Can it be defined?</li>
<li>Which CSP should I use for Severless?</li>
<li>Which Sereverless Framework or tool should I use?</li>
<li>How do you deploy for serverless?</li>
<li>How do you architect for serverless?</li>
<li>What is serverless?</li>
<li>What is a serverless function (vs a lambda)?</li>
<li>Does it take longer time to build a serverless app than a traditional app?</li>
<li>How does a serverless database work differently than a traditional database?</li>
<li>I heard serverless is cheaper, that it can scale to zero, is it true?</li>
<li>What technoloy, langauges, frameworks are you using to build an app?</li>
<li>What is a big challenge with serverless for adoption? (local development)</li>
<li>There is lot of experts using things like CQRS, Hexagon patterns, Single Table Design, Do I have to use all this stuff to get started? Will I be using serverless wrong if I mix in traditional tech like I'm use to? </li>
<li>Are using opionated serverless frameworks and platforms like Supabase, Amplify or Firebase a good or bad idea? </li>
</ul>
<h3 id="what-can-we-do-with-cloud-platforms">What can we do with Cloud Platforms?</h3>
<ul>
<li>Whats the difference between a cloud platform and a CSP?</li>
</ul>
<h3 id="linuxunix-knowledge-for-cloud">Linux/UNIX knowledge for Cloud</h3>
<ul>
<li>Which Unix distrubtionis most popular</li>
<li>Is there a reason I should use a CSP distro eg. Amazon Lunx</li>
<li>What are the essential linux skills I need to know?</li>
<li>What Linux distros do cloud providers generally have?</li>
<li>Which Linux distros are supported, which ones should you avoid?</li>
</ul>
<h3 id="programming-skills-needed-for-cloud">Programming Skills needed for Cloud</h3>
<ul>
<li>How much programming do I need to know to work with cloud?</li>
<li>What languages should I learn?</li>
<li>Can I have a cloud role and not program?</li>
</ul>
<h3 id="cloud-certifications">Cloud Certifications</h3>
<ul>
<li>Expert Questions by Multi-Certification holders</li>
<li>Is Alibaba, Oracle Cloud or OpenStack worth?</li>
<li>How transferable is certification knowledge to other certifications in different clouds?</li>
<li>Who is hiring based on certifications</li>
<li>Can you learn them all?</li>
</ul>
<h3 id="cloud-roles">Cloud Roles</h3>
<ul>
<li>What are they, want do the mean </li>
<li>How do I get that that title?</li>
</ul>
<h3 id="burning-platforms-and-digital-transformation">Burning Platforms and Digital Transformation</h3>
<ul>
<li>Predicting the future of cloud</li>
</ul>
<h3 id="cloud-cost-economics">Cloud Cost Economics</h3>
<ul>
<li>How to use the cloud and be frugal</li>
<li>What are common cost traps early on?</li>
<li>What cost traps that happen later on?</li>
<li>What do I do if my bill is exploding?</li>
<li>How do I stay on top of my bill?</li>
<li>What's the best way to visualize common costs?</li>
</ul>
<h3 id="how-to-be-creative-in-the-age-of-cloud">How to be creative in the age of cloud</h3>
<ul>
<li>Write something creative here!</li>
</ul>
<h3 id="public-sector-for-cloud">Public Sector for Cloud</h3>
<ul>
<li>How does the government use the cloud?</li>
<li>What do I need to get to work in the Public Sector for cloud? Certifications...</li>
<li>How hard is it to build workloads that can be used by Cloud eg. GovCloud</li>
</ul>
<h3 id="cloud-governance">Cloud Governance</h3>
<ul>
<li>How do mange cloud at the organizational level?</li>
</ul>
<h3 id="hybrid-cloud">Hybrid Cloud</h3>
<ul>
<li>How do I gain hybrid experience when I don't work for an enterprise with its own datacenter?</li>
<li>How can I run my own little on-premise to simulate hybrid connections</li>
<li>Is their still a demand for traditional skills or are all the Hybrid jobs for the people who already worked on-premise and transitioned to cloud?</li>
</ul>
<h3 id="virtualization-and-cloud">Virtualization and Cloud</h3>
<ul>
<li>What do I need to know about VMs?</li>
<li>Where do I learn about VMs?</li>
<li>Are VMs going to be around or are they going to vanish soon eg. Containers and Serverless</li>
</ul>
<h3 id="aws-vs-azure-vs-gcp">AWS vs Azure vs GCP?</h3>
<ul>
<li>Which is the best for beginners </li>
<li>Which is the best for long-term careers?</li>
<li>Can you learn them all at once or just learn one?</li>
</ul>
<h3 id="cloud-motivated">Cloud Motivated</h3>
<ul>
<li>How do you stay motivated during your entire journey? </li>
</ul>
<h3 id="cloud-for-newcomers">Cloud for Newcomers</h3>
<ul>
<li>How do you break into cloud when your a fresh college or university graduate?</li>
</ul>
<h3 id="cloud-for-latecomers">Cloud for Latecomers</h3>
<ul>
<li>How do you break into cloud when your 30+, 40+, 50+ and beyond?</li>
</ul>
<h3 id="cloud-for-notechers">Cloud for Notechers</h3>
<ul>
<li>How do transition to cloud with no background in technology whatsoever?</li>
</ul>
<h3 id="cloud-accessibility">Cloud Accessibility</h3>
<ul>
<li>Is cloud careers, cloud giants and cloud technology accessible? </li>
</ul>
<h3 id="cloud-for-teens">Cloud for Teens</h3>
<ul>
<li>When's the right time to be teaching cloud? </li>
<li>How do make it fun? </li>
<li>How do we keep young folks interested and engaged?</li>
</ul>
<h3 id="identity-for-cloud">Identity for Cloud</h3>
<ul>
<li>Zero Trust Model</li>
<li>Redefining security boundaries<ul>
<li>the new mobile workforce</li>
</ul>
</li>
<li>how much is too much monitor of employees of security?</li>
<li>BeyondCorp, JumpCloud, Azure AD </li>
<li>Does SSO make a big difference for companies?</li>
</ul>
<h3 id="cloud-conferences">Cloud Conferences</h3>
<ul>
<li>Is re:Invent, Microsoft Ignite, Google Next worth it? </li>
<li>Do online conference work? </li>
<li>How to get your company to pay your ticket</li>
<li>How to make best use of your time?</li>
<li>Online vs In-Person what's the big difference?</li>
</ul>
<h3 id="mlops-for-beginners">MLOps for Beginners</h3>
<ul>
<li>Do I really need to know ML? ML vs MLOps. </li>
<li>Is it expensive to learn MLOps</li>
</ul>
<h3 id="cloud-data-for-beginners">Cloud Data For Beginners</h3>
<ul>
<li>Where BI meets the cloud datastores. </li>
<li>Should I use a mix of CSPs? </li>
<li>It is easy to become a Data Engineers, Data Analytics, Data Scientist</li>
</ul>
<h3 id="learning-in-the-open">Learning in the Open</h3>
<ul>
<li>How learning in the open will help you with your career and your cloud journey. </li>
<li>What structured things are out there?</li>
</ul>
<h3 id="open-source-for-cloud">Open Source for Cloud</h3>
<ul>
<li>When to use an open-source backed system over a cloud service</li>
<li>Fully Managed Open-source cloud services or run on your own VM?</li>
</ul>
<h3 id="cloud-fundamentals">Cloud Fundamentals</h3>
<ul>
<li>Should experts learn the fundamentals? </li>
<li>What is considered complete for cloud fundamentals?</li>
</ul>
<h3 id="cloud-resumes">Cloud Resumes</h3>
<ul>
<li>LinkedIn</li>
<li>Polywork </li>
<li>the old-fashion resume</li>
<li>what should we be doing for cloud to optimize?</li>
</ul>
<h3 id="cloud-recruiters">Cloud Recruiters</h3>
<ul>
<li>Who are they? </li>
<li>How do you get their attention?</li>
<li>How do you know they are legit?</li>
<li>Should you work with them to get a job?</li>
<li>Do all cloud recruiters take a cut of your salary? </li>
<li>Any other tips?</li>
</ul>
<h3 id="cloud-enterprise">Cloud Enterprise</h3>
<ul>
<li>How do we get big companies in the cloud?</li>
<li>What do you need to know to work in enterprise and cloud that is different from a startup?</li>
<li>What are challenges Enterprises facing in the cloud different from smaller companies?</li>
<li>What has been the most interesting thing Enterprises have been doing with the cloud?</li>
</ul>
<h3 id="multi-cloud">Multi-Cloud</h3>
<ul>
<li>Is it multi-cloud if you are using workloads in isolate? </li>
<li>Services that make multi-cloud easy</li>
<li>Common multi-cloud strategies</li>
<li>Is multi-cloud about using open-source systems across multiple cloud?</li>
<li>Is multi-cloud expensive?</li>
</ul>
<h3 id="climbing-the-tech-company-ladder">Climbing The Tech Company Ladder</h3>
<ul>
<li>How do you go from Tech Support Desk to Cloud Engineer?</li>
</ul>
<h3 id="cloud-bootcamps">Cloud Bootcamps</h3>
<ul>
<li>Who's doing them?</li>
<li>Are they work the money?</li>
<li>What is a reasonable outcome?</li>
</ul>
<h3 id="cloud-projects">Cloud Projects</h3>
<ul>
<li>What makes a good cloud project for a resume?</li>
<li>What are </li>
</ul>
<h3 id="secrets-of-cloud-spend">Secrets of Cloud Spend</h3>
<ul>
<li>Is setting a Budget or Billing alarm enough to stay on top of your bill?
eg. at our company its not enough and we have to schedule an event every month with at least two people. One to review one to hold the other accountable for spend</li>
<li>Is Multi-cloud a billing trap?
eg. Ingress and Outgress costs, you are dropping the best-of-breed?</li>
<li>Do CSPs have sane defaults where are the traps?
eg. When you launch an RDS its setup for production costs by default and takes some work to get to free tier</li>
<li>Should all CSPs have an initial sandbox?
 eg. Should experecing an overbilling incident and getting refunded be part of the learning experience?</li>
<li>Our CSPS and Cloud Platforms good or evil about billing eg.<ul>
<li>DataDog makes it hard to open a support ticket, they don't offer paid support so you can't escluate a billing incident, their marketing page obscure the cost of services and requires digging, its not easy to understand the breakdown of costs. You have to make a stink to get back a refund which you would expect not to be your fault.</li>
</ul>
</li>
<li>Should people be considering Second or Third Tier providers to control spend?</li>
<li>There is the concept of technical debt and running into a technical debt-end, is it the same with spend?
eg. When a company scales is it truley possible to get stuck with an expensive service you can't rip out? any examples? What is causing the pull?</li>
<li>What is the underlying reason of cost?
eg. is it Data Gravity wells?
eg. is it a company that wants to do and use everything in everying?
eg. it is Shadow IT? People want agility but they don't go through a centeralized tech dep</li>
<li>Should companies sit down an create a spend roadmap? It is something they can do on their own, or should they have a Cloud Economist just like having an Accouantant?</li>
<li>Is Cloud Economist services only really avaliable to larger companies or is their smaller providers eg. Accounting Firm vs Single Accountant</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building Real-Time Serverless Web Applications with AWS API Gateway WebSocket APIs]]></title><description><![CDATA[Real-Time Serverless Applications on AWS
 WebSocket technology has been around for nearly a decade and has become the standard for providing real-time web interactions without a need for special client software (beyond a browser) or polling operation...]]></description><link>https://blog.100daysofcloud.com/building-real-time-serverless-web-applications-with-aws-api-gateway-websocket-apis</link><guid isPermaLink="true">https://blog.100daysofcloud.com/building-real-time-serverless-web-applications-with-aws-api-gateway-websocket-apis</guid><category><![CDATA[AWS]]></category><category><![CDATA[websockets]]></category><category><![CDATA[serverless]]></category><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Matt Morgan]]></dc:creator><pubDate>Wed, 11 Aug 2021 19:53:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1626174678476/71MKrad20.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="real-time-serverless-applications-on-aws">Real-Time Serverless Applications on AWS</h1>
<p> <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API">WebSocket</a> technology has been around for nearly a decade and has become the standard for providing real-time web interactions without a need for special client software (beyond a browser) or polling operations. We can deliver amazing end-user experiences using this technology, but we also incur additional complexity and most likely cost. The operational overhead of needing to maintain persistent client connections can be considerable and becomes even more complex if our infrastructure needs to scale to handle shifts in user behavior.</p>
<p>AWS API Gateway WebSocket API is a fully managed service that can solve some of these problems for us. In this article, we'll compare WebSocket API to other solutions as we build out a real-time game and explore all the features of WebSocket API.</p>
<h1 id="table-of-contents">Table of Contents</h1>
<ol>
<li><a class="post-section-overview" href="#real-time-serverless-applications-on-aws">Real-Time Serverless Applications on AWS</a><ol>
<li><a class="post-section-overview" href="#practical-use-cases-for-web-sockets">Practical Use Cases for WebSockets</a><ol>
<li><a class="post-section-overview" href="#when-should-you-use-web-sockets">When Should You Use Web Sockets?</a></li>
</ol>
</li>
</ol>
</li>
<li><a class="post-section-overview" href="#project-summary">Project Summary</a></li>
<li><a class="post-section-overview" href="#websocket-api">WebSocket API</a><ol>
<li><a class="post-section-overview" href="#api-gateway-v2">API Gateway V2</a></li>
<li><a class="post-section-overview" href="#feature-completeness">Feature Completeness</a></li>
<li><a class="post-section-overview" href="#websocket-api-vs-appsync-subscriptions">WebSocket API vs AppSync Subscriptions</a></li>
<li><a class="post-section-overview" href="#websocket-api-vs-third-party-websocket-services">WebSocket API vs Third-Party WebSocket Services</a><ol>
<li><a class="post-section-overview" href="#pusher">Pusher</a></li>
<li><a class="post-section-overview" href="#socketio">Socket.IO</a></li>
<li><a class="post-section-overview" href="#ably">Ably</a></li>
<li><a class="post-section-overview" href="#actioncable">ActionCable</a></li>
<li><a class="post-section-overview" href="#firebase">Firebase</a></li>
<li><a class="post-section-overview" href="#aws-api-gateway-websocket-api">AWS API Gateway WebSocket API</a></li>
</ol>
</li>
<li><a class="post-section-overview" href="#websocket-api-vs-serverful">WebSocket API vs "serverful"</a></li>
<li><a class="post-section-overview" href="#your-favorite-infrastructure-as-code-solution">Your Favorite Infrastructure-as-Code Solution</a></li>
</ol>
</li>
<li><a class="post-section-overview" href="#building-the-real-time-app">Building the Real-Time App</a><ol>
<li><a class="post-section-overview" href="#architecture">Architecture</a></li>
<li><a class="post-section-overview" href="#database-design">Database Design</a></li>
</ol>
</li>
<li><a class="post-section-overview" href="#websocket-api-resources">WebSocket API Resources</a></li>
<li><a class="post-section-overview" href="#stages">Stages</a></li>
<li><a class="post-section-overview" href="#routing">Routing</a></li>
<li><a class="post-section-overview" href="#sending-messages">Sending Messages</a></li>
<li><a class="post-section-overview" href="#notifying-clients">Notifying Clients</a><ol>
<li><a class="post-section-overview" href="#scaling-up">Scaling Up</a></li>
</ol>
</li>
<li><a class="post-section-overview" href="#request-models">Request Models</a></li>
<li><a class="post-section-overview" href="#authorization">Authorization</a></li>
<li><a class="post-section-overview" href="#cost">Cost</a><ol>
<li><a class="post-section-overview" href="#estimate">Estimate</a></li>
<li><a class="post-section-overview" href="#throttling">Throttling</a></li>
</ol>
</li>
<li><a class="post-section-overview" href="#logging">Logging</a><ol>
<li><a class="post-section-overview" href="#cloudwatch-role">CloudWatch Role</a></li>
<li><a class="post-section-overview" href="#access-logs">Access Logs</a></li>
<li><a class="post-section-overview" href="#execution-logs">Execution Logs</a></li>
<li><a class="post-section-overview" href="#cloudwatch-metrics">CloudWatch Metrics</a></li>
</ol>
</li>
<li><a class="post-section-overview" href="#other-integrations">Other Integrations</a></li>
<li><a class="post-section-overview" href="#conclusion">Conclusion</a></li>
</ol>
<h2 id="practical-use-cases-for-web-sockets">Practical Use Cases for Web Sockets</h2>
<p>Here are some ideas on how you can apply WebSockets to your applications:</p>
<ul>
<li>Build a real-time chat messaging system</li>
<li>Build a multiplayer player video game over TCP in the web-browser</li>
<li>Build a real-time interface for a conversational AI chatbot</li>
<li>Push real-time notification to a notification bell</li>
<li>Create a shared interactive virtual environment </li>
<li>Create a collaborative whiteboard </li>
</ul>
<h3 id="when-should-you-use-web-sockets">When Should You Use Web Sockets?</h3>
<ul>
<li>When you want real-time interactions within your website or web-application</li>
<li>When you are building a game that does not depend on require twitch-like or frame-per-frame gameplay</li>
<li>When you are restricted to the limitation of the web browser for real-time interactions</li>
<li>When you want to implement a pub/sub integration with 3rd parties</li>
</ul>
<h1 id="project-summary">Project Summary</h1>
<p>To give an overview of the capabilities of WebSocket API, we are going to build a real-time game using:</p>
<ul>
<li>ApiGateway V2 WebSockets<ul>
<li>Custom Routing</li>
<li>Authorizer</li>
<li>Request Models</li>
<li>Logging</li>
</ul>
</li>
<li>React<ul>
<li>Static site in S3</li>
<li>Exposed to the Internet via Cloudfront</li>
</ul>
</li>
<li>Cloud Development Kit (CDK)<ul>
<li>Fullstack TypeScript</li>
<li>L2 constructs when possible</li>
<li>Expanded functionality via L1 constructs and Aspects</li>
</ul>
</li>
<li>DynamoDB<ul>
<li>Single Table Design</li>
<li>K-Sortable Unique IDentifier (KSUID)</li>
</ul>
</li>
</ul>
<h1 id="websocket-api">WebSocket API</h1>
<p> <a target="_blank" href="https://aws.amazon.com/blogs/compute/announcing-websocket-apis-in-amazon-api-gateway/">In 2018 AWS released WebSocket API</a> as a new offering for API Gateway. WebSocket API is a 100% serverless solution to managing client connections and communicating over WebSockets. We don't have to worry about provisioning servers to manage client connections using WebSocket API, nor will we incur expenses for unused, overprovisioned servers. This is a great tool to have, but there are still some limitations and implications to discuss.</p>
<h2 id="api-gateway-v2">API Gateway V2</h2>
<p>It's worth mentioning that WebSocket API is considered to be part of the API Gateway V2 specification. WebSocket API <a target="_blank" href="https://aws.amazon.com/blogs/compute/announcing-http-apis-for-amazon-api-gateway/">was joined by HTTP API in 2019</a> as the more widely-known V2 specification. The original (V1) API Gateway is also called REST API. This is relevant when it comes to CloudFormation templates ("AWS::ApiGatewayV2::Api") and feature completeness. Although it uses a very different protocol from HTTP API, WebSocket API has all the same model, authorization, logging, and traceability constructs as HTTP API. These are a bit different from the V1 spec used in REST API.</p>
<h2 id="feature-completeness">Feature Completeness</h2>
<p>We want to know whether we're in the V1 or V2 spec because V2 is very much a work in progress and isn't as feature complete as V1. This is most relevant when attempting to choose between REST API or HTTP API, but we should be aware of the limitations for WebSocket API. For example, at the time of this writing, WebSocket API doesn't support integration with AWS X-Ray, it doesn't support resource policies and API Gateway caching is not available. Unfortunately, there isn't a great resource for understanding the differences between the specifications, but the <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html">HTTP vs REST API comparison</a> is a good starting place.</p>
<h2 id="websocket-api-vs-appsync-subscriptions">WebSocket API vs AppSync Subscriptions</h2>
<p>AppSync, AWS's managed GraphQL service, <a target="_blank" href="https://aws.amazon.com/blogs/mobile/appsync-realtime/">announced support for GraphQL subscriptions in 2019</a>. GraphQL subscriptions use WebSockets to manage persistent connections with clients. This means you've got a choice of which serverless implementation you want to use for managing connections. How to choose?</p>
<p>This article will not do a deep dive into AppSync, but just to scratch the surface, the simplest decision point is whether or not we want to use GraphQL. If we want to use it, then you use AppSync. If not, then we use WebSocket API. That's pretty easy!</p>
<p>But maybe we care more about cost than specific technology choices. In that case, things get a lot more complicated, we need to understand our application and we need to do some math. WebSocket API charges $0.25 per million connection minutes in us-east-1 while AppSync charges just 8 cents for the same. However, WebSocket API charges $1 per million messages, and AppSync charges twice that. Do we want to optimize for clients sitting idle or optimize for chattiness? It depends on the app.</p>
<p>It's also worth mentioning that AppSync doesn't require us to store connection information in a database and may be more suitable for operating at a very high scale. We'll discuss WebSocket API connection management a bit later.</p>
<h2 id="websocket-api-vs-third-party-websocket-services">WebSocket API vs Third-Party WebSocket Services</h2>
<p>There are several third-party libraries and services that we can use to build real-time applications. How do they compare to WebSocket API?</p>
<h3 id="pusher">Pusher</h3>
<p><a target="_blank" href="https://pusher.com/">Pusher</a> is a SaaS product that provides real-time functionality in the form of "channels" or "beams", basically chat rooms or broadcast messages. Pusher exposes an API and client libraries, so it's not a drop-in replacement for other WebSocket technologies. Pusher has a generous free tier allowing 100 concurrent connections and 200k messages per day. If you go beyond that, the pricing model is fixed, unlike the pay-per-use model of API Gateway.</p>
<h3 id="socketio">Socket.IO</h3>
<p>This library is quite literally the reason I got into Node.js. It is a fantastic library, but it is a library. You will still need to deploy and manage your infrastructure. If that isn't a deal-breaker, then <a target="_blank" href="https://socket.io/">Socket.IO</a> is open-source software at its best.</p>
<h3 id="ably">Ably</h3>
<p>Similar to Pusher, <a target="_blank" href="https://ably.com/">Ably</a> provides real-time communication as a service. They have an API and SDK, so again you are writing against the provider. Pricing looks almost identical to Pusher.</p>
<h3 id="actioncable">ActionCable</h3>
<p>Another library, <a target="_blank" href="https://guides.rubyonrails.org/action_cable_overview.html">ActionCable</a>, is built into the Ruby on Rails framework. If you already have a Rails app, this might be a no-brainer, but it's probably not a compelling enough reason to create one on its own if you don't. You'll still need to manage your infrastructure.</p>
<h3 id="firebase">Firebase</h3>
<p>Firebase is a serverless backend from Google. Among many other features, it includes <a target="_blank" href="https://firebase.google.com/docs/firestore/query-data/listen">real-time updates</a>. If Firebase is a good overall solution for your application, this would be worth checking out. If you have no other reason to be using Firebase, this probably isn't enough to make it interesting.</p>
<h3 id="aws-api-gateway-websocket-api">AWS API Gateway WebSocket API</h3>
<p>WebSocket API can be managed with your favorite Infrastructure-as-Code such as <a target="_blank" href="https://aws.amazon.com/cdk/">AWS CDK</a>, <a target="_blank" href="https://aws.amazon.com/cloudformation/">CloudFormation</a>, <a target="_blank" href="https://www.terraform.io/">Terraform</a>, <a target="_blank" href="https://www.pulumi.com/">Pulumi</a>, <a target="_blank" href="https://arc.codes/">Architect</a>, <a target="_blank" href="https://www.serverless.com/">Serverless</a>, etc. You can integrate with other AWS services like Cognito, Lambda, S3, or DynamoDB. WebSocket API has a low price point for getting started and the cost scales with use. You're able to use compliance and auditing tools like AWS Config and Cloudtrail to manage, remediate and monitor access. WebSocket API can send access and execution logs to CloudWatch and stream to other logging sources.</p>
<p>In short, WebSocket API is part of the AWS ecosystem and comes with all the benefits a managed AWS service has, including support contracts and a vibrant community.</p>
<h2 id="websocket-api-vs-serverful">WebSocket API vs "serverful"</h2>
<p>In a traditional server model for WebSockets, the server will hold the connection open, requiring a small amount of memory. The Node.js single-threaded event loop architecture is ideal for this, but even so, we will likely wish to have pooled servers for redundancy and eventual scale. Doing this means that for users connected to server A to receive messages sent by users on server B, we need to maintain our list of connected clients in some external database or cache. Redis is often used for this. <strong>We also face the problem that a scaling event or removal of an operational node can require end-users to reconnect and miss messages.</strong> There are plenty of ways to mitigate this risk, but it requires additional coding or other layers of redundancy which can increase cost or complexity.</p>
<p>As we'll see, working with WebSocket API doesn't get us out of having to track connected clients, but it does mean we don't need to be concerned with scaling events or reliability. When it comes to spiky or unpredictable traffic patterns, the serverless approach can lead to significant savings in both cost and operational overhead.</p>
<p>Another advantage of the serverless approach we'll see is that we don't need to implement any kind of server-side WebSocket protocol. This is handled for us by WebSocket API. If we wish to implement a WebSocket implementation in Node.js to run on a server, we may use a library like <a target="_blank" href="https://socket.io/">Socket.IO</a>, which is a great library, but we have to accept the ongoing maintenance of managing the dependency, considering updates, configuring it, etc. When we use WebSocket API, that is all done for us.</p>
<h2 id="your-favorite-infrastructure-as-code-solution">Your Favorite Infrastructure-as-Code Solution</h2>
<p>I've written my app using <a target="_blank" href="https://aws.amazon.com/cdk/">AWS CDK</a>, which is my favorite way to manage infrastructure on AWS. I'll include some CloudFormation examples below as well as some console screenshots. It should be easy enough to bootstrap these examples into another framework like <a target="_blank" href="https://aws.amazon.com/serverless/sam/">SAM</a>, <a target="_blank" href="https://www.serverless.com/">Serverless</a>, or <a target="_blank" href="https://arc.codes/">Architect</a>.</p>
<p>As always, the AWS Console can be useful for learning more about AWS services or exploring the options, but you really should use some kind of Infrastructure-as-Code to manage your application.</p>
<p>The working application is <a target="_blank" href="https://github.com/elthrasher/planetstack">available on GitHub</a> and ready to be deployed into your AWS account. The app is written in TypeScript.</p>
<h1 id="building-the-real-time-app">Building the Real-Time App</h1>
<p>The standard WebSocket example is a chat application. AWS <a target="_blank" href="https://aws.amazon.com/blogs/compute/announcing-websocket-apis-in-amazon-api-gateway/">has a pretty good one</a> that I'm happy to recommend if that's what you're looking for. I wanted to do something a bit more elaborate. I thought it might be fun to build a little drag-and-drop SciFi base building game.</p>
<p>To gain inspiration, I purchased <a target="_blank" href="https://screamingbrainstudios.itch.io/planet-surface-backgrounds">Planet Surface Backgrounds</a> and <a target="_blank" href="https://wenrexa.itch.io/space-full-assets-pack">Space Modular Buildings</a> from <a target="_blank" href="https://itch.io/">itch.io</a>. With those assets, I wrote a <a target="_blank" href="https://github.com/elthrasher/planetstack/tree/main/app">game client</a> in React. I used React because it's the modern framework I have the most experience with and I find <a target="_blank" href="https://reactjs.org/docs/hooks-intro.html">Hooks</a> to be good for state management in a small app. I wanted to use a simple WebSocket client and I wanted drag-and-drop functionality. I went with <a target="_blank" href="https://github.com/robtaussig/react-use-websocket">React useWebSocket v2</a> (based on Hooks) and <a target="_blank" href="https://github.com/react-grid-layout/react-draggable">React-Draggable</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626142484043/xLeABJYFH.png" alt="planetstack.png" /></p>
<p>I decided I wanted my application to be able to change the background to one of the Planet Surface Backgrounds, to be able to add new building assets (from Space Modular Buildings), to be able to drag them around, and to be able to delete assets. I thought about adding the ability to rotate, resize or layer assets, but given I'm already updating position, this functionality would just involve adding new attributes to the building assets and so all the complexity would be in the CSS and I'm not trying to write an article on CSS. I'm poorly qualified to do so in fact!</p>
<h2 id="architecture">Architecture</h2>
<p>The fundamental paradigm here is that communication is one-way. A client sends a message such as "set the background to image #4" or "move asset #34 to x745y316". The server processes that and then it updates all connected clients. When my client makes a change, the server will process that and then my game state is updated along with all the other connected clients.</p>
<p>Now that we have an idea of what we want the app to do and what the architecture looks like, we can start thinking about an API. We know we'll want the following capabilities:</p>
<ul>
<li>Change Background</li>
<li>Add Icon</li>
<li>Move Icon</li>
<li>Delete Icon
We'll also need a way to pull the initial game state when a new client connects.</li>
<li>Get Game State
And finally, we'll need a way to connect and disconnect clients.</li>
<li>Connect</li>
<li>Disconnect</li>
</ul>
<p>This diagram demonstrates the AWS resources (aside from IAM) needed to build this application.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626142673425/wz4oPl8Kv.png" alt="psarch.png" /></p>
<p>Our web assets are stored in S3 and served via CloudFront. Clients will establish WebSocket connections via WebSocket API and invoke Lambda functions to connect, disconnect and access the game functions.</p>
<p>A great advantage of this kind of architecture is that we can have single-purpose functions. This is great for keeping our codebase simple and clean. The functions I've written range from 10-30 lines of code with a couple of shared resources, the biggest of which weighs in at a hefty 44 lines of code (omitting comments). Being able to create sophisticated interactive web applications with so few lines is why we build on the cloud and is a superpower of serverless architectures.</p>
<h2 id="database-design">Database Design</h2>
<p>We'll look at each of our seven functions, but first, let's consider the database. Those who know me won't be surprised to learn I've chosen DynamoDB. DynamoDB is a serverless NoSQL database. The provisioning and management of this database make it ideal for a sample project like this one. At the same time, a well-designed table can achieve a massive scale.</p>
<p>For this app, we have to track the game state and connected clients. These entities, though unrelated, can be stored in the same DynamoDB table, a pattern widely known as single-table design. The User entity I created is extremely minimal. Since the app doesn't track anything about the user, we only need to store the connection id that will be assigned by WebSocket API. We'll also set an expiration attribute to ensure old items are cleaned up.</p>
<p>For the game state, I thought about storing each icon in a separate table item but instead opted for a simpler design where the entire game state is stored as a single item. This item has an attribute that corresponds to the background we've chosen for the game and a map type that contains all the icons and their coordinates. Icons are identified by a KSUID - not that we necessarily need the IDs to be sortable, but it's something I usually have at hand when working with DynamoDB.
Now the game state payload looks something like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"bg"</span>: <span class="hljs-number">4</span>,
  <span class="hljs-attr">"icons"</span>: {
    <span class="hljs-attr">"icon1tl9wKMnwJIkc7wSTqPVFCy4zQe"</span>: {
      <span class="hljs-attr">"img"</span>: <span class="hljs-number">0</span>,
      <span class="hljs-attr">"x"</span>: <span class="hljs-number">474</span>,
      <span class="hljs-attr">"y"</span>: <span class="hljs-number">450</span>
    },
    <span class="hljs-attr">"icon1tl9xC1KWfkfXnuANJLFK1MYBoM"</span>: {
      <span class="hljs-attr">"img"</span>: <span class="hljs-number">1</span>,
      <span class="hljs-attr">"x"</span>: <span class="hljs-number">277</span>,
      <span class="hljs-attr">"y"</span>: <span class="hljs-number">448</span>
    }
  }
}
</code></pre>
<p>To save space, I'm only storing the index of the images chosen. We could save more space by using shorter unique identifiers or by using a list type and identifying icons only by position, but that could cause race conditions with multiple clients sending updates. This design is good enough. Never let perfect be the enemy of the good.</p>
<h1 id="websocket-api-resources">WebSocket API Resources</h1>
<p>Creating a WebSocket API is a lot like creating HTTP or REST API resources. We provide some basic configuration options, can add routing, models, and authorization. We can create a WebSocket API using AWS CDK using the WebSocketApi construct.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> webSocketApi = <span class="hljs-keyword">new</span> WebSocketApi(scope, <span class="hljs-string">'WebSocketApi'</span>, {});
</code></pre>
<p>When we synth this to CloudFormation and clean up the names a little, we can see some of the defaults that were set.</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">WebSocketApi:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGatewayV2::Api</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-string">WebSocketApi</span>
      <span class="hljs-attr">ProtocolType:</span> <span class="hljs-string">WEBSOCKET</span>
      <span class="hljs-attr">RouteSelectionExpression:</span> <span class="hljs-string">$request.body.action</span>
</code></pre>
<h2 id="stages">Stages</h2>
<p>The intent of API Gateway stages appears to be that we should map /dev, /test, /prod, and so on all to the same API Gateway resource. This has never really made sense to me since even though we can <a target="_blank" href="https://medium.com/swlh/aws-api-gateway-point-each-stage-to-different-versions-of-a-lambda-function-8a68d65445b4">select Lambda versions</a> using stage variables, it would still mean that my test stage has an IAM role that lets it execute production compute. Instead deploy your dev, test, and prod into <a target="_blank" href="https://aws.amazon.com/organizations/getting-started/best-practices/">separate accounts</a>. I don't know of anybody who uses multiple stages per API Gateway.</p>
<p>Nevertheless, we need at least one stage. It can be defined very easily in CDK.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> stage = <span class="hljs-keyword">new</span> WebSocketStage(scope, <span class="hljs-string">'DevStage'</span>, {
    autoDeploy: <span class="hljs-literal">true</span>,
    stageName: <span class="hljs-string">'dev'</span>,
    webSocketApi: webSocketApi,
  });
</code></pre>
<p>Or CloudFormation.</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">DevStage:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGatewayV2::Stage</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ApiId:</span>
        <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApi</span>
      <span class="hljs-attr">StageName:</span> <span class="hljs-string">dev</span>
      <span class="hljs-attr">AutoDeploy:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>AutoDeploy is another odd one here. Presumably, if we set that to false (which is the <a target="_blank" href="https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigatewayv2.WebSocketStage.html#autodeployspan-classapi-icon-api-icon-experimental-titlethis-api-element-is-experimental-it-may-change-without-noticespan">default</a> in AWS CDK), then we'll need to trigger a deployment manually from the AWS console instead of expecting it to happen as part of our stack deployment. I'm not sure why we'd want that as a default. To make it even stranger, CloudFormation defaults this value to <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-stageoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-stageoverrides-autodeploy">true</a>.</p>
<h2 id="routing">Routing</h2>
<p>If we're using AWS CDK, our RouteSelectionExpression will default to $request.body.action. This will perform routing based on the action attribute of the JSON payload for each message we send to WebSocket API. Other IaC solutions may require this attribute to be set explicitly to perform custom routing.</p>
<p>We can also configure our connect and disconnect routes as well as a default route when creating the WebSocket API. In this application, I chose not to use a default route and instead focus on single-purpose functions. If I'd wanted to, I could've created a "lambdalith" that handles routing internally with all messages coming via the default route. Note that the choice to use custom routing means that we must use JSON as the payload for our WebSocket messages. If we wanted to send plain text or something else, we could still do that via WebSocket API, but we'd be limited to the default route for routing.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> webSocketApi = <span class="hljs-keyword">new</span> WebSocketApi(scope, <span class="hljs-string">'WebSocketApi'</span>, {
    connectRouteOptions: { integration: <span class="hljs-keyword">new</span> LambdaWebSocketIntegration({ handler: fns.onConnect }) },
    disconnectRouteOptions: { integration: <span class="hljs-keyword">new</span> LambdaWebSocketIntegration({ handler: fns.onDisconnect }) },
  });
</code></pre>
<p>This CDK code binds our onConnect and onDisconnect functions to the $connect and $disconnect routes of WebSocket API. A WebSocket client will use these routes automatically.</p>
<p>Here's the generated CloudFormation (with simplified names) for the onConnect route.</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">ConnectRouteIntegration:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGatewayV2::Integration</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ApiId:</span>
        <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApi</span>
      <span class="hljs-attr">IntegrationType:</span> <span class="hljs-string">AWS_PROXY</span>
      <span class="hljs-attr">IntegrationUri:</span>
        <span class="hljs-attr">Fn::Join:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">""</span>
          <span class="hljs-bullet">-</span> <span class="hljs-bullet">-</span> <span class="hljs-string">"arn:"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">Ref:</span> <span class="hljs-string">AWS::Partition</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">:apigateway:us-east-1:lambda:path/2015-03-31/functions/</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">Fn::GetAtt:</span>
                <span class="hljs-bullet">-</span> <span class="hljs-string">onConnectFunction</span>
                <span class="hljs-bullet">-</span> <span class="hljs-string">Arn</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">/invocations</span>
  <span class="hljs-attr">ConnectRoute:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGatewayV2::Route</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ApiId:</span>
        <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApi</span>
      <span class="hljs-attr">RouteKey:</span> <span class="hljs-string">$connect</span>
      <span class="hljs-attr">Target:</span>
        <span class="hljs-attr">Fn::Join:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">""</span>
          <span class="hljs-bullet">-</span> <span class="hljs-bullet">-</span> <span class="hljs-string">integrations/</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">Ref:</span> <span class="hljs-string">ConnectRouteIntegration</span>
</code></pre>
<p>The YAML starts to get a bit verbose, but those experienced with other API Gateway implementations will recognize how functions are bound to routes. It works just the same in WebSocket API.</p>
<p>We can use AWS CDK to add custom routes.</p>
<pre><code class="lang-typescript">  webSocketApi.addRoute(<span class="hljs-string">'addIcon'</span>, {
    integration: <span class="hljs-keyword">new</span> LambdaWebSocketIntegration({ handler: fns.addIcon }),
  });
</code></pre>
<p>This produces similar CloudFormation (again simplified names).</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">AddIconRouteIntegration:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGatewayV2::Integration</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ApiId:</span>
        <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApi</span>
      <span class="hljs-attr">IntegrationType:</span> <span class="hljs-string">AWS_PROXY</span>
      <span class="hljs-attr">IntegrationUri:</span>
        <span class="hljs-attr">Fn::Join:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">""</span>
          <span class="hljs-bullet">-</span> <span class="hljs-bullet">-</span> <span class="hljs-string">"arn:"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">Ref:</span> <span class="hljs-string">AWS::Partition</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">:apigateway:us-east-1:lambda:path/2015-03-31/functions/</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">Fn::GetAtt:</span>
                <span class="hljs-bullet">-</span> <span class="hljs-string">AddIconFunction</span>
                <span class="hljs-bullet">-</span> <span class="hljs-string">Arn</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">/invocations</span>
  <span class="hljs-attr">AddIconRoute:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGatewayV2::Route</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ApiId:</span>
        <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApi</span>
      <span class="hljs-attr">RouteKey:</span> <span class="hljs-string">addIcon</span>
      <span class="hljs-attr">Target:</span>
        <span class="hljs-attr">Fn::Join:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">""</span>
          <span class="hljs-bullet">-</span> <span class="hljs-bullet">-</span> <span class="hljs-string">integrations/</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">Ref:</span> <span class="hljs-string">AddIconIntegration</span>
</code></pre>
<p>When we send a message, WebSocket API will examine the "action" attribute of the payload. If that matches "addIcon" (the RouteKey), then this route will be selected and AddIconFunction will be invoked with the payload.</p>
<h1 id="sending-messages">Sending Messages</h1>
<p>WebSocket messages can be sent as text or binary frames. To use JSON, we'll need the built-in JavaScript JSON object to serialize our objects. WebSocket API will be able to deserialize the payload and check the "action" property, but we'll also need to deserialize again in our Lambda handler using JSON.parse().</p>
<p>The WebSocket client we've chosen gives us an easy way to serialize JSON by exposing <a target="_blank" href="https://github.com/robtaussig/react-use-websocket#sendjsonmessage">sendJsonMessage</a>, which wraps its own <a target="_blank" href="https://github.com/robtaussig/react-use-websocket#sendmessage">sendMessage</a> function with a JSON.stringify call. We can simply prepare a payload and call the function.</p>
<pre><code class="lang-typescript">sendJsonMessage({ action: <span class="hljs-string">'addIcon'</span>, icon: { img: <span class="hljs-number">3</span>, x: <span class="hljs-number">400</span>, y: <span class="hljs-number">400</span> } });
</code></pre>
<p>This instructs the backend to add icon #3 to the x400 y400 coordinates of the screen. Because communication is one-way, we don't need to await a response or resolve a promise. If WebSocket API responds with an error, the error should be handled by the onError handler of our client library.</p>
<p>I like type safety, so I added some <a target="_blank" href="https://github.com/elthrasher/planetstack/blob/main/types/message.ts#L4">types</a> and a simple <a target="_blank" href="https://github.com/elthrasher/planetstack/blob/main/app/providers/WSProvider.tsx#L57">wrapper</a> with a TypeScript generic and here is the resulting <a target="_blank" href="https://github.com/elthrasher/planetstack/blob/main/app/components/chooser.tsx#L18">invocation</a>.</p>
<pre><code class="lang-typescript">sendMessage&lt;AddIconModel&gt;({ action: MessageAction.ADD_ICON, icon: { img, x: <span class="hljs-number">400</span>, y: <span class="hljs-number">400</span> } });
</code></pre>
<p>Now that we've notified the backend that a change has occurred, we need to notify connected clients and send out the new game state.</p>
<h1 id="notifying-clients">Notifying Clients</h1>
<p>Sending a JSON payload to a backend is pretty standard behavior for a web app. If that's all we were trying to do here, HTTP API would do the job just fine. To unlock the real benefits of WebSockets, we need our backend to update all connected clients. To do that we'll need to use <a target="_blank" href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/apigatewaymanagementapi.html">ApiGatewayManagementApi</a>. You've got to love a service that both starts and ends with "Api".</p>
<p>Unfortunately, ApiGatewayManagementApi (let's call it AGMA) doesn't provide any mechanism for broadcasting to connected clients. We can use it to <a target="_blank" href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/apigatewaymanagementapi.html#ApiGatewayManagementApi.Client.get_connection">fetch a connection by client id</a>, <a target="_blank" href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/apigatewaymanagementapi.html#ApiGatewayManagementApi.Client.delete_connection">delete a connection</a>, or <a target="_blank" href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/apigatewaymanagementapi.html#ApiGatewayManagementApi.Client.post_to_connection">post a message to a connection</a>. AGMA has a few other utility methods, but nothing that will help us. This means that we need to manage a list of connected clients and further means that the logic for "notify all clients" is that we fetch a list of the clients and then loop over the list, notifying them one-by-one.</p>
<p>This has real implications of scale for WebSocket API. If you're thinking about building an application that manages millions of connected clients, WebSocket API probably isn't suitable for the task at this time. Hopefully, it is on the roadmap to add broadcast and channel capabilities to WebSocket API.</p>
<p>I wrote some <a target="_blank" href="https://github.com/elthrasher/planetstack/blob/main/repositories/management.ts">helper functions</a> for working with AGMA. Whenever we update the game state, we can call notifyClients to broadcast the new game state. This function will pull all the client connections from the database, iterate over the connectionIds and post the message to each of them. If it turns out that one of the clients has disconnected, AGMA will respond with an HTTP 410 (Gone) code and we can clean the stale connection from our database.</p>
<h2 id="scaling-up">Scaling Up</h2>
<p>So what's the realistic scale for something like this? I tried some light benchmarking using a tool called <a target="_blank" href="https://github.com/observing/thor">thor</a> (an older tool that needs a <a target="_blank" href="https://github.com/observing/thor/issues/45#issuecomment-323985735">workaround</a> to install - would be glad for suggestions on something better). Managing a thousand connected clients pushed my Lambda invocation times out to 40-50 seconds. I'm sure that could be optimized, but we start to hit practical limits for the one-at-a-time approach WebSocket API limits us to. With this in mind, I think WebSocket API will still work great for real-time interactions shared by tens of users. Of course, we can introduce our own channel abstractions to limit the number of connected clients we have to manage at one time, but this will require more custom code.</p>
<h1 id="request-models">Request Models</h1>
<p>The ability to validate payloads against a JSON schema is one of the best and most overlooked features of API Gateway. All versions of API Gateway support this, including WebSocket API. Payload validation can be messy to implement with imperative logic in application code. For this reason, it can be the source of bugs or outright omitted, leading to unexpected application behavior or invalid data. Implementing request validation in API Gateway gives us a good separation of concerns, protects our application code from invalid data, and best of all bears no additional expense. API Gateway is billed per request. The additional compute required to perform validation is free if we let API Gateway handle it, and if the validation fails, our application code is never invoked, thus never billed.</p>
<p>Models are expressed in <a target="_blank" href="https://datatracker.ietf.org/doc/html/draft-zyp-json-schema-04">JSON Schema v4</a>. Unfortunately, AWS CDK does not provide a higher level construct to add models to API Gateway V2 as it does for V1. This means we will need to use <a target="_blank" href="https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_l1_using">L1 Constructs</a> to define models in CDK. This can be done using the CfnModel construct.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">new</span> CfnModel(scope, <span class="hljs-string">`AddModel`</span>, {
    apiId: webSocketApi.apiId,
    contentType: <span class="hljs-string">'application/json'</span>,
    name: <span class="hljs-string">`AddModel`</span>,
    schema: {
      $schema: <span class="hljs-string">'http://json-schema.org/draft-04/schema#'</span>,
      properties: {
        action: { <span class="hljs-built_in">enum</span>: [MessageAction.ADD_ICON] },
        icon: {
          properties: {
            id: { <span class="hljs-keyword">type</span>: JsonSchemaType.STRING },
            img: { <span class="hljs-keyword">type</span>: JsonSchemaType.NUMBER },
            x: { <span class="hljs-keyword">type</span>: JsonSchemaType.NUMBER },
            y: { <span class="hljs-keyword">type</span>: JsonSchemaType.NUMBER },
          },
          required: [<span class="hljs-string">'img'</span>, <span class="hljs-string">'x'</span>, <span class="hljs-string">'y'</span>],
          <span class="hljs-keyword">type</span>: JsonSchemaType.OBJECT,
        },
      },
      required: [<span class="hljs-string">'action'</span>, <span class="hljs-string">'icon'</span>],
      title: <span class="hljs-string">`AddSchema`</span>,
      <span class="hljs-keyword">type</span>: <span class="hljs-string">'object'</span>,
    },
  });
</code></pre>
<p>This synths to CloudFormation.</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">AddModel:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGatewayV2::Model</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ApiId:</span>
        <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApi34BCF99B</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-string">AddModel</span>
      <span class="hljs-attr">Schema:</span>
        <span class="hljs-string">$schema:</span> <span class="hljs-string">http://json-schema.org/draft-04/schema#</span>
        <span class="hljs-attr">properties:</span>
          <span class="hljs-attr">action:</span>
            <span class="hljs-attr">enum:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-string">addIcon</span>
          <span class="hljs-attr">icon:</span>
            <span class="hljs-attr">properties:</span>
              <span class="hljs-attr">id:</span>
                <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
              <span class="hljs-attr">img:</span>
                <span class="hljs-attr">type:</span> <span class="hljs-string">number</span>
              <span class="hljs-attr">x:</span>
                <span class="hljs-attr">type:</span> <span class="hljs-string">number</span>
              <span class="hljs-attr">"y":</span>
                <span class="hljs-attr">type:</span> <span class="hljs-string">number</span>
            <span class="hljs-attr">required:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-string">img</span>
              <span class="hljs-bullet">-</span> <span class="hljs-string">x</span>
              <span class="hljs-bullet">-</span> <span class="hljs-string">"y"</span>
            <span class="hljs-attr">type:</span> <span class="hljs-string">object</span>
        <span class="hljs-attr">required:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">action</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">icon</span>
        <span class="hljs-attr">title:</span> <span class="hljs-string">AddSchema</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">object</span>
      <span class="hljs-attr">ContentType:</span> <span class="hljs-string">application/json</span>
</code></pre>
<p>In order to attach a model to a route, we need to cast WebSocketRoute to CfnRoute, as models are missing from the L2 WebSocketApi construct.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> route = webSocketApi.addRoute(MessageAction.ADD_ICON, {
    integration: <span class="hljs-keyword">new</span> LambdaWebSocketIntegration({ handler: fns.addIcon }),
  });
  <span class="hljs-keyword">const</span> rt = route.node.defaultChild <span class="hljs-keyword">as</span> CfnRoute;

  rt.modelSelectionExpression = <span class="hljs-string">'$request.body.action'</span>;
  rt.requestModels = { [action]: model.name };

  rt.addDependsOn(model);
</code></pre>
<p>The same in CloudFormation.</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">WebSocketApiaddIconRoute:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGatewayV2::Route</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ApiId:</span>
        <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApi</span>
      <span class="hljs-attr">RouteKey:</span> <span class="hljs-string">addIcon</span>
      <span class="hljs-attr">ModelSelectionExpression:</span> <span class="hljs-string">$request.body.action</span>
      <span class="hljs-attr">RequestModels:</span>
        <span class="hljs-attr">addIcon:</span> <span class="hljs-string">AddModel</span>
      <span class="hljs-attr">Target:</span>
        <span class="hljs-attr">Fn::Join:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">""</span>
          <span class="hljs-bullet">-</span> <span class="hljs-bullet">-</span> <span class="hljs-string">integrations/</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApiaddIconRouteWebSocketIntegration</span>
    <span class="hljs-attr">DependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AddModel</span>
</code></pre>
<p>To avoid repetitive imperative code, I wrote a few helper functions to make it easier to generate routes and attach models.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> getModel = <span class="hljs-function">(<span class="hljs-params">
  scope: Stack,
  modelName: <span class="hljs-built_in">string</span>,
  properties: {
    [name: <span class="hljs-built_in">string</span>]: JsonSchema;
  },
  required: <span class="hljs-built_in">string</span>[],
  webSocketApi: WebSocketApi,
</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CfnModel(scope, <span class="hljs-string">`<span class="hljs-subst">${modelName}</span>Model`</span>, {
    apiId: webSocketApi.apiId,
    contentType: <span class="hljs-string">'application/json'</span>,
    name: <span class="hljs-string">`<span class="hljs-subst">${modelName}</span>Model`</span>,
    schema: {
      $schema: <span class="hljs-string">'http://json-schema.org/draft-04/schema#'</span>,
      properties,
      required,
      title: <span class="hljs-string">`<span class="hljs-subst">${modelName}</span>Schema`</span>,
      <span class="hljs-keyword">type</span>: <span class="hljs-string">'object'</span>,
    },
  });
};
</code></pre>
<p>This will produce a model ready to use in the given webSocketApi.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> addRoute = <span class="hljs-function">(<span class="hljs-params">handler: LambdaFunction, action: MessageAction, model: CfnModel, webSocketApi: WebSocketApi</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> route = webSocketApi.addRoute(action, {
    integration: <span class="hljs-keyword">new</span> LambdaWebSocketIntegration({ handler }),
  });
  <span class="hljs-keyword">const</span> rt = route.node.defaultChild <span class="hljs-keyword">as</span> CfnRoute;

  rt.modelSelectionExpression = <span class="hljs-string">'$request.body.action'</span>;
  rt.requestModels = { [action]: model.name };

  rt.addDependsOn(model);
};
</code></pre>
<p>And here we can pass the model and Lambda integration to extend <code>addRoute</code> capabilities to include the model and selection expression. Including <code>addDependsOn</code> is necessary to create stack dependencies for updates and deletions. This is something an L2 construct will do implicitly but since we are using L1, we need to manage these dependencies or CloudFormation may return the dreaded "UPDATE_ROLLBACK_FAILED" state.</p>
<p>An issue I noticed when working with models is that I wanted to set <code>additionalProperties: false</code> on my model to fail validation if there should be properties in the payload that are not described by the model. When I tried this, my deployment failed with "Invalid model schema specified". I can't find any official documentation on this property but I suspect this is a bug in API Gateway V2 as I'm able to use this property in V1 models and it's valid in the JSON Schema. The only clue I've found is an old <a target="_blank" href="https://stackoverflow.com/a/62581246/1487358">stack overflow response</a> that suggests at one time, <code>additionalProperties</code> wasn't supported by API Gateway at all.</p>
<p>A final limitation I've found is that a good design pattern using JsonSchema is to have one model reference another using $ref. This is outlined in <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html">API Gateway docs</a>, but under the REST API heading. V2 docs simply point to the V1 docs. I have been able to get $ref to work with REST API but wasn't able to get it to work with WebSocket API. This might be a limitation in the V2 API.</p>
<p>It's not clear why these limitations appear in the V2 service. What's interesting is that when working in the AWS console, I see requests to both the <code>apigateway</code> path as well as <code>apigatewayv2</code>. It seems some features are reused from V1, so maybe they aren't interacting perfectly with the newer V2 APIs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626176543829/0MXC3FVHm.png" alt="console1.png" /></p>
<h1 id="authorization">Authorization</h1>
<p>All versions of API Gateway support custom Lambda authorizers, but as with models, WebSocket API is lacking an L2 construct for CDK. Unlike models, WebSocket API is alone in its lack of an L2 Lambda authorizer construct. HTTP API recently got one, so we can hold out hope that WebSocket API will have one.</p>
<p>The official docs do a decent job of going over the <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-lambda-auth.html">anatomy of an authorizer function</a>, but unlike the RESTful API implementations, WebSocket API has no mention of a Cognito integration, but there is a <a target="_blank" href="https://github.com/aws-samples/websocket-api-cognito-auth-sample">pretty good example app using Cognito on GitHub</a>.</p>
<p>So can we add an authorizer to our application? Yes! But it will be for demonstration purposes only, logging out the request and then passing the user through. The most common way to authorize users is to access a <a target="_blank" href="https://jwt.io/">JSON Web Token</a> in the user request and validate it. To do that in our game, we'd need to add a login page and some kind of token issuer (possibly Cognito). This is beyond the scope of this article.</p>
<p>Since there isn't an L2 Authorizer construct that works with WebSocket API, we'll need to use L1 constructs if we want to define our application in CDK.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> authorizer = <span class="hljs-keyword">new</span> CfnAuthorizer(scope, <span class="hljs-string">'Authorizer'</span>, {
    apiId: webSocketApi.apiId,
    authorizerType: <span class="hljs-string">'REQUEST'</span>,
    authorizerUri: Stack.of(scope).formatArn({
      account: <span class="hljs-string">'lambda'</span>,
      resource: <span class="hljs-string">'path'</span>,
      resourceName: <span class="hljs-string">`2015-03-31/functions/<span class="hljs-subst">${authFn.functionArn}</span>/invocations`</span>,
      service: <span class="hljs-string">'apigateway'</span>,
    }),
    name: <span class="hljs-string">'WebSocketApiAuthorizer'</span>,
  });

  authFn.addPermission(<span class="hljs-string">'AuthZPermission'</span>, {
    principal: <span class="hljs-keyword">new</span> ServicePrincipal(<span class="hljs-string">'apigateway.amazonaws.com'</span>),
    sourceArn: Stack.of(webSocketApi).formatArn({
      service: <span class="hljs-string">'execute-api'</span>,
      resource: (webSocketApi.node.defaultChild <span class="hljs-keyword">as</span> CfnApi).ref,
      resourceName: <span class="hljs-string">`authorizers/<span class="hljs-subst">${authorizer.ref}</span>`</span>,
    }),
  });
</code></pre>
<p>One cool thing about using Authorizers with WebSocket API is that, unlike most RESTful APIs, we only have to authorize on the <code>$connect</code> route. This means our Authorizer function will only be invoked once per user session and we only have to attach it to the <code>$connect</code> route. We could attempt to use a cast to find the route, but for this use case, since we don't actually instantiate a <code>WebSocketRoute</code>, it's easier to do this using an <a target="_blank" href="https://docs.aws.amazon.com/cdk/latest/guide/aspects.html">Aspect</a>. Aspects are special CDK constructs that can inspect and modify constructs. They can be used for compliance, but in this case, we'll use one to set properties that aren't available on the L2 construct.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> AuthZAspect <span class="hljs-keyword">implements</span> IAspect {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> authorizer: CfnAuthorizer</span>) {}
  <span class="hljs-keyword">public</span> visit(node: IConstruct) {
    <span class="hljs-keyword">if</span> (node <span class="hljs-keyword">instanceof</span> CfnRoute &amp;&amp; node.routeKey === <span class="hljs-string">'$connect'</span>) {
      node.authorizerId = <span class="hljs-built_in">this</span>.authorizer.ref;
      node.authorizationType = <span class="hljs-string">'CUSTOM'</span>;
    }
  }
}

Aspects.of(webSocketApi).add(<span class="hljs-keyword">new</span> AuthZAspect(authorizer));
</code></pre>
<p>Of course this all synths to CloudFormation, or it could just be written that way.</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">Authorizer:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGatewayV2::Authorizer</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ApiId:</span>
        <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApi</span>
      <span class="hljs-attr">AuthorizerType:</span> <span class="hljs-string">REQUEST</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-string">WebSocketApiAuthorizer</span>
      <span class="hljs-attr">AuthorizerUri:</span>
        <span class="hljs-attr">Fn::Join:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">""</span>
          <span class="hljs-bullet">-</span> <span class="hljs-bullet">-</span> <span class="hljs-string">"arn:"</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">Ref:</span> <span class="hljs-string">AWS::Partition</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">:apigateway:us-east-1:lambda:path/2015-03-31/functions/</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">Fn::GetAtt:</span>
                <span class="hljs-bullet">-</span> <span class="hljs-string">authorizerFunction</span>
                <span class="hljs-bullet">-</span> <span class="hljs-string">Arn</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">/invocations</span>

  <span class="hljs-attr">WebSocketApiconnectRoute:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGatewayV2::Route</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ApiId:</span>
        <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApi</span>
      <span class="hljs-attr">RouteKey:</span> <span class="hljs-string">$connect</span>
      <span class="hljs-attr">AuthorizationType:</span> <span class="hljs-string">CUSTOM</span>
      <span class="hljs-attr">AuthorizerId:</span>
        <span class="hljs-attr">Ref:</span> <span class="hljs-string">Authorizer</span>
      <span class="hljs-attr">Target:</span>
        <span class="hljs-attr">Fn::Join:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">""</span>
          <span class="hljs-bullet">-</span> <span class="hljs-bullet">-</span> <span class="hljs-string">integrations/</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApiconnectRouteWebSocketIntegration</span>
</code></pre>
<h1 id="cost">Cost</h1>
<p>To get any kind of accurate cost estimate, we'll need deep knowledge of our application and how it will be used. The AWS free tier is pretty good on serverless projects, so I don't expect to have a bill for the game. For production-grade applications, we need to think about how many clients will connect, how long they'll stay connected, will they mostly listen for messages or will they be chatty, wanting to broadcast messages to other connected clients? The answers to these questions will have a monumental impact on the cost of running your application.</p>
<h2 id="estimate">Estimate</h2>
<p>When it comes to estimating costs, the two things to think about are connection minutes and messages sent. If we had a thousand clients who stayed connected for eight hours per day, that would be 480 (minutes) <em> 30 (days) </em> 1000 (clients) = 14.4 million minutes per month billed at $0.25 per day (in us-east-1) for a cost of $3.60 per month.</p>
<p>We also need to look at the total number of messages sent. If we sent a message every minute to every one of those clients that would give us 14.4 million messages for $14.40 (in us-east-1). However, if the app were extremely chatting sending a message every second, the cost would shoot up to $864 per month, not including the cost for compute, storage, data transfer, and so on.</p>
<h2 id="throttling">Throttling</h2>
<p>One way to control costs or at least help prevent cost overruns is to implement throttling in an API. This can be done in any version of API Gateway and is a best practice. API Gateway, like most AWS services, has <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html#apigateway-execution-service-websocket-limits-table">quotas and rate limits</a> out of the box. We can further protect our wallets by implementing throttling at the <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-protect.html#websocket-api-protect-throttling-account">account</a> or <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-protect.html#websocket-api-protect-throttling-route">route</a> level.</p>
<p>If we think our API won't get a lot of traffic, it is a very good idea to implement throttling as it will prevent a runaway client (perhaps a result of a bug causing an endless loop) from generating a large bill. The throttling limit is per second. If we set our limit to 10, then the maximum bill we could generate for requests in a month is $26.94, and that's just if our API was hit nonstop and served up all 26,280,000 requests!</p>
<p>To manage throttling with higher expected throughput, combine it with a <a target="_blank" href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/monitor_estimated_charges_with_cloudwatch.html">billing alarm</a>. Billing alarms do not trigger immediately. Imagine we've deployed an API Gateway and some errant process started running up the bill. If it ran for 24 hours at the default account-level throttling rate of 10,000 requests per second, we'd have a bill of $864, having served 864 million requests in that period! If we neglected to set up a billing alarm, we'd be looking at a serious expense for this API after 30 days. It's a best practice to enable throttling unless we think we're going to need to serve 10,000 requests per second.</p>
<p>Throttling is configured at the API Gateway stage. Unfortunately, it is not implemented in the CDK WebSocketStage construct, so to implement throttling, we need to cast the construct to <code>CfnStage</code>. This will allow us to set <code>defaultRouteSettings</code>.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> stage = <span class="hljs-keyword">new</span> WebSocketStage(scope, <span class="hljs-string">'DevStage'</span>, {
    autoDeploy: <span class="hljs-literal">true</span>,
    stageName,
    webSocketApi,
  });

  <span class="hljs-keyword">const</span> cfnStage = stage.node.defaultChild <span class="hljs-keyword">as</span> CfnStage;
  cfnStage.defaultRouteSettings = {
    throttlingBurstLimit: <span class="hljs-number">500</span>,
    throttlingRateLimit: <span class="hljs-number">1000</span>,
  };
</code></pre>
<p>The CDK code starts to resemble CloudFormation when we use L1 constructs.</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">DevStage:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGatewayV2::Stage</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ApiId:</span>
        <span class="hljs-attr">Ref:</span> <span class="hljs-string">WebSocketApi</span>
      <span class="hljs-attr">StageName:</span> <span class="hljs-string">dev</span>
      <span class="hljs-attr">AutoDeploy:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">DefaultRouteSettings:</span>
        <span class="hljs-attr">ThrottlingBurstLimit:</span> <span class="hljs-number">500</span>
        <span class="hljs-attr">ThrottlingRateLimit:</span> <span class="hljs-number">1000</span>
</code></pre>
<h1 id="logging">Logging</h1>
<p>Lambda functions automatically create CloudWatch logs when they are executed and so if we are working with Lambda integrations, we can get some level of logging with no extra effort. However, this is only going to be as good as our integration. If we have misconfigured something in the WebSocket API and the function never gets called, then we're going to want the WebSocket API to be logging as well to avoid the frustrating situation of our app being broken and having no idea why.</p>
<h2 id="cloudwatch-role">CloudWatch Role</h2>
<p>Getting logging right for WebSocket API is a bit of a bear and unfortunately, the official docs aren't much help. The first and most important thing we're going to have to do is create a role to allow API Gateway to send to CloudWatch. If you're an AWS CDK user with some REST API experience, like I am, you may be used to the more mature <code>RestApi</code> construct managing all this for you automatically. The <code>apigatewayv2</code> constructs don't seem to have the same feature and so we must create the role manually. This could be done in the console, but I wouldn't recommend it. We'll just have to write a little more code.</p>
<p>I haven't found a lot of information about managing this role with CloudFormation, but from what I'm able to tell, we need to create the role, then specify the role ARN to the <code>AWS::ApiGateway::Account</code> resource. Oddly there's no <code>AWS::ApiGatewayV2::Account</code> resource so we need to use <code>AWS::ApiGateway::Account</code>. This means in CDK we'll have to use the CfnAccount L1 construct from <code>@aws-cdk/aws-apigateway</code>. This feels like a big hack and I'd be delighted to know of another way to manage this (outside of the console - a bigger hack).</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> cwRole = <span class="hljs-keyword">new</span> Role(scope, <span class="hljs-string">'CWRole'</span>, {
    assumedBy: <span class="hljs-keyword">new</span> ServicePrincipal(<span class="hljs-string">'apigateway.amazonaws.com'</span>),
    managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName(<span class="hljs-string">'service-role/AmazonAPIGatewayPushToCloudWatchLogs'</span>)],
  });

  <span class="hljs-keyword">new</span> CfnAccount(scope, <span class="hljs-string">'Account'</span>, {
    cloudWatchRoleArn: cwRole.roleArn,
  });
</code></pre>
<h2 id="access-logs">Access Logs</h2>
<p>Now that we have a role, we can enable logging. API Gateway can provide access logs and execution logs. We can format the access log and choose from a <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-logging.html">list of parameters</a>. The access log will produce a single line in the log for each request made to our WebSocket API. This kind of logging is similar to Apache or Nginx access logging.</p>
<p>To enable access logging in CDK, we again will need to cast WebSocketStage to CfnStage. We'll also need to create a log group to target.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> stage = <span class="hljs-keyword">new</span> WebSocketStage(scope, <span class="hljs-string">'DevStage'</span>, {
    autoDeploy: <span class="hljs-literal">true</span>,
    stageName,
    webSocketApi,
  });
  <span class="hljs-comment">// Manage the log group</span>
  <span class="hljs-keyword">new</span> LogGroup(scope, <span class="hljs-string">'ExecutionLogs'</span>, {
    logGroupName: <span class="hljs-string">`/aws/apigateway/<span class="hljs-subst">${webSocketApi.apiId}</span>/<span class="hljs-subst">${stageName}</span>`</span>,
    removalPolicy: RemovalPolicy.DESTROY,
    retention: RetentionDays.ONE_WEEK,
  });
  <span class="hljs-keyword">const</span> log = <span class="hljs-keyword">new</span> LogGroup(scope, <span class="hljs-string">'AccessLogs'</span>, {
    removalPolicy: RemovalPolicy.DESTROY,
    retention: RetentionDays.ONE_WEEK,
  });
  <span class="hljs-keyword">const</span> cfnStage = stage.node.defaultChild <span class="hljs-keyword">as</span> CfnStage;
  cfnStage.accessLogSettings = {
    destinationArn: log.logGroupArn,
    format: <span class="hljs-string">`$context.identity.sourceIp - - [$context.requestTime] "$context.httpMethod $context.routeKey $context.protocol" $context.status $context.responseLength $context.requestId`</span>,
  };
  cfnStage.defaultRouteSettings = {
    throttlingBurstLimit: <span class="hljs-number">500</span>,
    throttlingRateLimit: <span class="hljs-number">1000</span>,
  };
</code></pre>
<h2 id="execution-logs">Execution Logs</h2>
<p>API Gateway execution logs are far more verbose than access logs and contain detailed information on the internals of API Gateway. If we encounter a stubborn validation bug, we'll want to enable execution logs to learn why API Gateway is rejecting our request. However, these logs are so verbose that we could run up a high CloudWatch bill if we leave them enabled all the time. The best practice is usually to disable execution logs and only re-enable them when we think we need them.</p>
<p>Execution logs are configured in the same place as throttling, the <code>defaultRouteSettings</code>. We can assign values to dataTraceEnabled, detailedMetricsEnabled, and <code>loggingLevel</code>. The last is the most important. The values allowed are 'ERROR', 'INFO', and 'OFF'. The other two properties are a little harder to understand. When I set dataTraceEnabled to true, I see "Log full message data" checked in the AWS console.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626177498828/VUZ7eqEUB.png" alt="console2.png" /></p>
<p>This isn't what I'd expect a property called "dataTraceEnabled" to do. I would expect that to add trace IDs to our requests or something similar. If I set the property to false, I see this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626177535476/VCLLQUXLd.png" alt="console3.png" /></p>
<p>And now the logs are considerably less verbose. The docs aren't helping me much here, but apparently "dataTraceEnabled" is what we want if we want to see full request bodies.</p>
<p>The other property, "detailedMetricsEnabled", suggests we might see additional CloudWatch metrics if we enable it, however, I don't see anything in the console that corresponds to this property, and setting it didn't have any apparent effect, nor is there any information in the documentation on this property.</p>
<p>After combining my logging properties with the throttling properties, I now have my defaultRouteSettings ready to go.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> stage = <span class="hljs-keyword">new</span> WebSocketStage(scope, <span class="hljs-string">'DevStage'</span>, {
    autoDeploy: <span class="hljs-literal">true</span>,
    stageName,
    webSocketApi,
  });
  <span class="hljs-comment">// Manage the log group</span>
  <span class="hljs-keyword">new</span> LogGroup(scope, <span class="hljs-string">'ExecutionLogs'</span>, {
    logGroupName: <span class="hljs-string">`/aws/apigateway/<span class="hljs-subst">${webSocketApi.apiId}</span>/<span class="hljs-subst">${stageName}</span>`</span>,
    removalPolicy: RemovalPolicy.DESTROY,
    retention: RetentionDays.ONE_WEEK,
  });
  <span class="hljs-keyword">const</span> log = <span class="hljs-keyword">new</span> LogGroup(scope, <span class="hljs-string">'AccessLogs'</span>, {
    removalPolicy: RemovalPolicy.DESTROY,
    retention: RetentionDays.ONE_WEEK,
  });
  <span class="hljs-keyword">const</span> cfnStage = stage.node.defaultChild <span class="hljs-keyword">as</span> CfnStage;
  cfnStage.accessLogSettings = {
    destinationArn: log.logGroupArn,
    format: <span class="hljs-string">`$context.identity.sourceIp - - [$context.requestTime] "$context.httpMethod $context.routeKey $context.protocol" $context.status $context.responseLength $context.requestId`</span>,
  };
  cfnStage.defaultRouteSettings = {
    dataTraceEnabled: <span class="hljs-literal">true</span>,
    loggingLevel: <span class="hljs-string">'INFO'</span>,
    throttlingBurstLimit: <span class="hljs-number">500</span>,
    throttlingRateLimit: <span class="hljs-number">1000</span>,
  };
</code></pre>
<h2 id="cloudwatch-metrics">CloudWatch Metrics</h2>
<p>API Gateway will automatically send some <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-logging.html">metrics</a> to CloudWatch. We can use them to create CloudWatch dashboards or feed them into any of the popular 3rd party monitoring tools. CDK users might benefit from an open-source tool like <a target="_blank" href="https://github.com/cdklabs/cdk-watchful">CDK Watchful</a>.</p>
<h1 id="other-integrations">Other Integrations</h1>
<p>Our base-building game uses Lambda integrations, but it bears mentioning that like REST and HTTP API, WebSocket API has several different <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-requests.html">integration patterns</a>. This is a great feature of API Gateway that lets us integrate directly with AWS services like DynamoDB or S3, make REST calls to arbitrary endpoints, connect to VPC Links, or event return mock responses without having to supply our own compute layer.</p>
<p>These kinds of integrations usually involve writing <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-data-transformations.html">mapping templates</a> which can be challenging at first but can be well worth it as you still get all the other great benefits of API Gateway, like throttling, authorization, request models, and so on.</p>
<h1 id="conclusion">Conclusion</h1>
<p>I spent a lot of time in this article on some of the less well-defined features of WebSocket API, but that shouldn't distract from the core which is a fully managed service that is highly performant, highly available, capable of serving 10,000 requests per second without increasing latency, and pay-for-use. WebSocket API is probably not suitable for broadcasting messages to thousands or millions of consumers - AppSync might fit that use case better - but there are many use cases for smaller real-time applications.</p>
<p>As a CDK user, I've called out the places where CDK support isn't complete, requiring us to drop down to L1 constructs to make full use of API Gateway features. Although I was able to make this work, developers interested in using CDK to develop WebSocket APIs should make sure the feature requests are there. L1 constructs usually take more time to work with as we don't get the great default settings and typing support out of CDK and so this is a drawback, even if we have workarounds.</p>
<p>WebSocket API is great fun to build on. As someone who has built other WebSocket applications, WebSocket API is a great development accelerator and abstracts away a lot of the hard parts about working with WebSockets at scale. However, the need to manage user connections is a drawback and can be a dealbreaker depending on the number of users we need to send messages to.</p>
<p>First on my wishlist for WebSocket API is that it will gain support for channels and broadcasts, similar to Socket.IO's <a target="_blank" href="https://socket.io/docs/v4/broadcasting-events/">broadcast</a> and <a target="_blank" href="https://socket.io/docs/v4/rooms/">room</a> features. I would also like to see better CDK support and L2 constructs to make working with WebSocket API easier. As for the latter, there are some <a target="_blank" href="https://github.com/aws/aws-cdk/issues/13869">GitHub issues</a> already addressing CDK support.</p>
<p>COVER: Sköll and Hati chase Sól and Mani. PUBLIC DOMAIN</p>
]]></content:encoded></item><item><title><![CDATA[Running Ruby on Rails with AWS Lambda (CloudDive)]]></title><description><![CDATA[CloudDive is a monthly podcast presented by the 100DaysOfCloud community where we dive deep into specific cloud topics or projects with an expert guest.
 🎙️ Listen to the podcast 🎙️ 
This accompanying article expands on the original podcast topic t...]]></description><link>https://blog.100daysofcloud.com/running-ruby-on-rails-with-aws-lambda-clouddive</link><guid isPermaLink="true">https://blog.100daysofcloud.com/running-ruby-on-rails-with-aws-lambda-clouddive</guid><category><![CDATA[AWS]]></category><category><![CDATA[serverless]]></category><category><![CDATA[Ruby on Rails]]></category><category><![CDATA[aws lambda]]></category><dc:creator><![CDATA[Andrew Brown]]></dc:creator><pubDate>Thu, 19 Nov 2020 00:33:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1605745740105/8ffVOgGk_.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>CloudDive</strong> is a monthly podcast presented by the 100DaysOfCloud community where we dive deep into specific cloud topics or projects with an expert guest.</p>
<p><a target="_blank" href="https://anchor.fm/100daysofcloud/episodes/CloudDive-EP1---Rails-on-Lambda-with-Ken-Collins-emjlvb"> 🎙️ Listen to the podcast 🎙️ </a></p>
<p>This accompanying article expands on the original podcast topic to help provide more technical information if you are looking to implement Ruby on Rails with AWS Lambda using Lamby.</p>
<h2 id="what-is-lamby">What is Lamby?</h2>
<p><a target="_blank" href="https://lamby.custominktech.com/">Lamby</a> is a <strong>ruby gem</strong> that enables you to run your Ruby on Rails application on AWS Lambda with little-to-modification to your existing project. </p>
<h2 id="a-return-to-the-golden-age">A Return To the Golden Age</h2>
<p>Imagine the productivity and happiness of Ruby on Rails but with nearly-zero DevOps responsibility. You might be thinking, well such a service exists and it's called Heroku or AWS Elastic Beanstalk. </p>
<p>But there are clear differences between your PaaS and your Serverless compute service:</p>
<ul>
<li>simpler tools for deployment</li>
<li>billing that occurs at 100ms instead of hourly</li>
<li>no hassle configurations like setting up callbacks in appspec.yml </li>
<li>synergies with existing serverless tooling</li>
</ul>
<p>And if you have reservations I appreciate the skepticism. The purpose of this CloudDive is to answer all the possible questions and we are fortunate to be accompanied on this dive by Ken Collins.</p>
<h3 id="ken-collins">Ken Collins</h3>
<p>Ken has a love for both Ruby and Serverless, currently is the Principal Engineer at <a target="_blank" href="https://www.customink.com/">CustomInk</a>. Ken is the creator of the Lamby, the open-source gem that allows you to run Ruby on Rails in AWS Lambda. This one of his many open-source contributions to ruby and serverless so it makes that AWS has recognized him as an <a target="_blank" href="https://aws.amazon.com/developer/community/heroes/ken-collins/">AWS Serverless Hero</a></p>
<p>If you want to connect with Ken you can find him on various social media platforms:</p>
<ul>
<li>https://dev.to/metaskills</li>
<li>https://www.linkedin.com/in/metaskills/</li>
<li>https://twitter.com/metaskills</li>
</ul>
<h2 id="understanding-how-lamby-works">Understanding How Lamby Works</h2>
<h3 id="aws-lambda-recap">AWS Lambda Recap</h3>
<p>Let us do a quick recap on how AWS Lambda works. AWS Lambda is a Function as a Service (FaaS). Meaning that you choose your runtime, upload your code and the service takes care of everything else.</p>
<p>The entry point to a Lambda function is its handler.  We can name our handler whatever we want and in this case, its called <code>app.handler</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605665406979/tjjKDpnD5W.png" alt="Screen Shot 2020-11-17 at 9.09.38 PM.png" /></p>
<p>That means Lambda expects us to have a ruby file called <code>app.rb</code> and it will call the function named <code>handler</code>. AWS Lambda has an inline editor where you can edit your handler function.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605665535195/-IEVyAeO4.png" alt="Screen Shot 2020-11-17 at 9.12.01 PM.png" /></p>
<p>So you might mistakenly think Lambda is intended to contain only a single function but the reality is that Lambda just runs a single function. You can have as many functions and file as you like. In the end, it just going to call that handler.</p>
<p>So how much code can we place into a Lambda? According to the <a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html#limits-list">Lambda Limits</a> 
once our code exceeds 3 MB we can no longer use the inline editor and have to upload a zip deployment package.</p>
<p>We are allowed 50MB compressed and when it uncompressed onto Lambda we're allowed 250MB including Lambda layers. So we have lots of room to work with here so we don't have to keep thinking small with Lambda.</p>
<h3 id="lamby-package">Lamby Package</h3>
<p>So to prepare your Rails application for Lamby you're going to add the Lamby gem to your Gemfile and within your Rails Root directory you'll create your Lambda handler which in this case is named <code>app.rb</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605666742760/0IRtcglBM.png" alt="deployment-package.png" /></p>
<p>And here is the contents of that <code>app.rb</code></p>
<pre><code class="lang-rb">ENV[<span class="hljs-string">'RAILS_SERVE_STATIC_FILES'</span>] = <span class="hljs-string">'1'</span>
require_relative <span class="hljs-string">'config/boot'</span>
<span class="hljs-keyword">require</span> <span class="hljs-string">'dotenv'</span> ; Dotenv.load <span class="hljs-string">".env.<span class="hljs-subst">#{ENV[<span class="hljs-string">'RAILS_ENV'</span>]}</span>"</span>
<span class="hljs-keyword">require</span> <span class="hljs-string">'lamby'</span>
require_relative <span class="hljs-string">'config/application'</span>
require_relative <span class="hljs-string">'config/environment'</span>

$app = Rack::Builder.new { run Rails.application }.to_app

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handler</span><span class="hljs-params">(<span class="hljs-symbol">event:</span>, <span class="hljs-symbol">context:</span>)</span></span>
  Lamby.handler $app, event, context, <span class="hljs-symbol">rack:</span> <span class="hljs-symbol">:http</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>So there are two important lines. The first is that when Rails boots up via our <code>config/boot</code> it will initialize our application via <code>Rails.application</code>, then we are using <code>Rack::Builder.new</code> to construct a Rack application.</p>
<pre><code>$app = Rack::Builder.<span class="hljs-keyword">new</span> { run Rails.application }.to_app
</code></pre><p>Then we are passing our Rack application to Lamby, and the Lamby gem sprinkles in a bit of magic.</p>
<pre><code>Lamby.handler $app, event, context, <span class="hljs-symbol">rack:</span> <span class="hljs-symbol">:http</span>
</code></pre><p>One of the nice things about Lamby is that the gem is incredibly small. You can open up the gem's lib directory and quickly make sense of said magic that it is sprinkled within.</p>
<p>If you've ever maintained a Rails app for a decade, there is a fear of a convenient but very complex gem that you become reliant on but then eventually becomes unmaintained. So it's a relief to see that it's very simple and easy to understand.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605668995106/uvJ67dszV.png" alt="Screen Shot 2020-11-17 at 10.08.01 PM.png" /></p>
<h2 id="the-story-with-rack">The story with Rack</h2>
<p>So why so do we need to wrap our Rails app with a Rack app? Well, the truth is that you probably always have been. If you have ever used a web-server such as Unicorn, Puma or Phusion Passenger, they will wrap your Ruby on Rails app as a Rack app.</p>
<p>Rack is a web server interface, meaning that it acts as an adapter by standardizing how it handles HTTP requests and responses. </p>
<p>Rails and many other ruby web-frameworks are rack-compatible. So for the most part Lamby runs Rack apps. I say for the most part because some of the magic is optimized for Rails, but you can run other web-frameworks in Lamby such as Sinatra though you'll have to check with Ken.</p>
<h2 id="getting-started-the-easy-way">Getting Started the Easy Way</h2>
<p>So there is a bit more than just adding a single <code>app.rb</code> file to your Rails app, zipping and uploading to Lambda. You will need to package all of your gems into your Rails vendor directory.</p>
<p>Ken has provided us with a good example application that has all the scripts we'll need to:</p>
<ul>
<li>package the gems</li>
<li>create a SAM application with needed infrastructure eg. API Gateway, Lambda</li>
<li>create a local development environment via docker</li>
<li>deploy our application</li>
</ul>
<p>So if you can get the SAM CLI installed you can scaffold this example project:</p>
<pre><code>sam <span class="hljs-keyword">init</span> --location <span class="hljs-string">"gh:customink/lamby-cookiecutter"</span>
</code></pre><p>And once you have a copy of this example project this you can just copy over the necessary files to your application:</p>
<ul>
<li>app.rb</li>
<li>Makefile</li>
<li>bin/*</li>
</ul>
<p>I know there are a lot of lingering questions so let's get into them:</p>
<h2 id="okay-but-what-about">Okay, But what about...</h2>
<p>So these are all the questions I had for Ken that had me still on the fence about running Ruby on Rails with Lambda.</p>
<h3 id="but-isnt-it-a-bad-idea-to-load-an-entire-web-application-framework-into-a-lambda">But isn't it a bad idea to load an entire web-application framework into a Lambda?</h3>
<p>Many programmers think of SOLID or Single Principle Design (SPD) and interpret AWS Lambda to be a single function in nature. <a target="_blank" href="https://hackernoon.com/aws-lambda-should-you-have-few-monolithic-functions-or-many-single-purposed-functions-8c3872d4338f">If you were to ask the room, they would be split between single or multi-use lambdas</a>.</p>
<p>Lambda is just a form of computing and we have in the past already been running a collection of functions that act as a service or domain in containers or a larger collection of functions that act as a monolith on a VM.</p>
<p>It has become very popular in recent years to run a single containerized version of Ruby on Rails, so why not think of Lambda as a managed pre-defined serverless container?</p>
<h3 id="but-isnt-there-already-a-serverless-way-to-run-rails-using-the-ruby-on-jets-framework">But isn't there already a serverless way to run Rails using the Ruby on Jets framework?</h3>
<p>Ruby on Jets existed before AWS supported an official ruby runtime and its approach to serverless Rails reflects that. Ruby on Jets does use Rails as its underlying libraries but there is a lot of boilerplate on top making it an entirely different framework.</p>
<p>Instead of deploying an entire Rails app to a Lambda it will take each of your Rails controller actions and deploy them to individual Lambdas all backed by an API Gateway endpoint. It will map your Rails routes as the schema for your API Gateway. There is much more to the Ruby on Jets framework.</p>
<p>So you are buying into a very opinionated framework that entangles and distributes your Rails application with multiple serverless servers. This could be good or bad depending on you feel about the architecture and what kind of flexibility you'll need in your serverless architecture going forward.</p>
<p>With Lamby you have a lot more freedom in your architecture because you simply are deploying a Rails app onto a Lambda.</p>
<h3 id="but-how-does-routing-work">But how does routing work?</h3>
<p>You have a single Lambda, but you have multiple Rails routes, so you're thinking how are all these different endpoints going to map to a single Lambda function.</p>
<p>So your Lambda is either going to be backed by API Gateway REST, API Gateway HTTP or an Application Load Balancer.</p>
<pre><code class="lang-rb"> Lamby.handler $app, event, context, <span class="hljs-symbol">rack:</span> <span class="hljs-symbol">:http</span>
 Lamby.handler $app, event, context, <span class="hljs-symbol">rack:</span> <span class="hljs-symbol">:rest</span>
 Lamby.handler $app, event, context, <span class="hljs-symbol">rack:</span> <span class="hljs-symbol">:alb</span>
</code></pre>
<blockquote>
<p>fun fact, when using <code>:http</code>  it supports both v1 and v2 payloads via auto-detection.</p>
</blockquote>
<p>So let us say we have a web-application that defines the following Rails routes:</p>
<pre><code><span class="hljs-keyword">GET</span> /tasks
<span class="hljs-keyword">GET</span> /tasks/:id
<span class="hljs-keyword">GET</span> /tasks/<span class="hljs-built_in">new</span>
<span class="hljs-keyword">GET</span> /tasks/:id/edit
POST /tasks
PUT /tasks/:id
<span class="hljs-keyword">DELETE</span> /tasks/:id
<span class="hljs-keyword">GET</span> /projects
<span class="hljs-keyword">GET</span> /projects/:id
<span class="hljs-keyword">GET</span> /projects/<span class="hljs-built_in">new</span>
<span class="hljs-keyword">GET</span> /projects/:id/edit
POST /projects
PUT /projects/:id
<span class="hljs-keyword">DELETE</span> /projects/:id
</code></pre><p>Using API Gateway REST you would think you need to create a route for each of the following routes that route to Lambda. Luckily API Gateway REST supports a $default route which will act as a catch-all.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605713098459/mXq3x4UNb.png" alt="Screen Shot 2020-11-18 at 10.24.38 AM.png" /></p>
<p>When the HTTP request is forwarded to your Lambda it will have the full path of the request and so Rails will treat the HTTP request as it normally would, using the you Rails route to send it to the corresponding controller action.</p>
<p>API Gateway has <a target="_blank" href="https://aws.amazon.com/blogs/aws/api-gateway-update-new-features-simplify-api-development/">catch-all variable paths</a> so it can support this behaviour as well.</p>
<p>Application Load Balancer will by default route all your routes to your Lambda, and with request routing, you can specify subpaths with wildcards so if you just wanted to lift a specific part of your app to a Rails Lamba you could.</p>
<p>If you wanted to break your large Rails app into smaller isolate Rails apps based on the domain and have them all sitting on the same domain with different subpaths you can do this and it's strongly encouraged</p>
<pre><code><span class="hljs-regexp">/accounts/</span>* =&gt; Service that handles self-managed user account 
<span class="hljs-regexp">/billing/</span>* =&gt; Service handles purchases, subscription <span class="hljs-keyword">and</span> billing details
</code></pre><h3 id="but-how-do-i-connect-to-a-database">But how do I connect to a database?</h3>
<p>In your Rails app, it would be business as usual with your Postgres or MySQL gem for the database driver. These gems rely on native-extensions so you'll likely want a pre-compiled gem that contains the underlying c binary file.</p>
<p>So for MySQL there Ken has given us <a target="_blank" href="https://github.com/customink/mysql2-lambda">mysql2-lambda</a></p>
<p>For Postgres uncertain right now, but I'll update this article when I found out since it's my primary database. </p>
<p>There are a couple of challenges when connecting AWS Lambda to relational databases. </p>
<p>You need a way of pooling database connections because Lambda can overwhelm the database connection limits of your RDS instance. So this would require you to set up a pooling server so in the case of Postgres this would be using <a target="_blank" href="https://www.pgbouncer.org/">pg_bouncer</a>. AWS now has a managed pooling service you can turn on in AWS Lambda called RDS Proxy with AWS Lambda.</p>
<p>In order for AWS Lamdba to connect to your RDS instance, it will need to reside in the same VPC. You can change AWS Lambda from the AWS managed VPC to your own.</p>
<p>If you want to use <a target="_blank" href="https://github.com/customink/activerecord-aurora-serverless-adapter">Aurora Serverless and Data API</a> Ken has also written an adapter gem for that as well. I think in this case because Data API transverses the internet you don't need to place your Lambda in the same VPC.</p>
<p>If you don't want Data API to leave the AWS network you can create a VPC endpoint to keep all traffic secure and safe inside of AWS.</p>
<h3 id="but-how-do-i-handle-background-jobs">But how do I handle background jobs?</h3>
<p>The most commonly used background job service used by Rails developers is Sidekiq. Sidekiq needs to run its own daemon in a traditional monolith running on a single server so you aren't going to be doing that on AWS Lambda. </p>
<p>Running Sidekiq on another Lambda is possible, at least the web-console, <a target="_blank" href="https://github.com/hoshinotsuyoshi/serverless-sidekiq-web">proven by this open-source project</a>.</p>
<p>Sidekiq is reliant on Redis and you aren't going to run that in a Lambda since the database is in-memory, and you can use ElastiCache to host your Redis server. ElastiCache has the limitation in that resources only within the same VPC can connect to your ElastiCache cluster. </p>
<p>Lambda functions can connect to ElastiCache but I don't fully the defaults to that so you'll have to have a fun time <a target="_blank" href="https://stackoverflow.com/questions/27759026/connect-to-elasticache-cluster-from-aws-lambda-function">following links on StackOverflow</a></p>
<p>So likely running Sidekiq on Lambda is not feasible or advisable. If you want that Sidekiq it's going to likely need to run a VM on EC2.</p>
<p>This would be a great opportunity to adopt SQS, which is AWS messaging queue. It's not as fast as Sidekiq but it's serverless and just scales. Ken has tutorialized <a target="_blank" href="https://lamby.custominktech.com/docs/activejob_and_lambda">How to use Shoryuken with ActiveJob</a></p>
<p><a target="_blank" href="https://github.com/phstc/shoryuken">Shoryuken</a> is a gem that allows you to write jobs as you normally would in Rails that connect to SQS. You may find the experience much easier than you think.</p>
<h3 id="but-what-about-rubys-that-require-native-extensions-c-compiled-libraries">But what about ruby's that require native-extensions? (c compiled libraries)</h3>
<p>Ruby gems that have native-extensions are gems that rely on binary c files. In order to use these gems you need to <a target="_blank" href="https://blog.francium.tech/using-ruby-gems-with-native-extensions-on-aws-lambda-aa4a3b8862c9">compile the underlying c programs</a> on the target operating system. </p>
<p>I think Ken has taken care of this for us within his cookie-cutter project because it will set up a docker container with the same Container used by AWS Lambda's ruby runtime.</p>
<p>And he has a script that will package all of the gems in the vendor directory and so I expect that it's going to compile native extensions.</p>
<h3 id="but-arent-rails-apps-large-and-doesnt-lambda-has-a-size-limit">But aren't Rails apps large and doesn't Lambda has a size limit?</h3>
<p>My ExamPro platform that's a very robust Ruby on Rails application currently shows 844 MB but after some digging most of this is because of log files, tmp files or assets. </p>
<p>After removing all these files from Rails application is around ~28 MB. When comparing to ExpressJS application where you would think you have a much smaller footprint what I have found is that node_modules alone can balloon out to 20 MB so they are quite comparable in size.</p>
<p>That being said you're going to likey not serve us your larger assets such as an image from your assets/images directory, so this will force you into best practice of placing those on S3 and CloudFront. </p>
<p>Again, Ken has solved this for us as well as he has tutoralized <a target="_blank" href="https://lamby.custominktech.com/docs/asset_host_and_precompiling">How to host and precompile your assets</a></p>
<p>So you can still use Webpacker or Sprockets as you normally would.</p>
<h3 id="but-doesnt-rails-have-a-very-slow-boot-time">But doesn't Rails have a very slow boot time?</h3>
<p>It depends on how large is your existing web-application. I think if you adopted Lamby on day 1, you'd be more adopted to breaking up your web-application into multiple Lamby's based on service domain and so a very slow boot time would not be problematic.</p>
<p>If the boot time does pose an issue, you can start your migration by breaking up domains within your Rails web-application into <a target="_blank" href="https://guides.rubyonrails.org/engines.html">Rails Engines</a>. </p>
<p>Rail Engine is a plugin system where you can self-contain Rails into a Gem and you can mount that gem into your existing Rails application allowing you to isolate the business-specific code to that engine.</p>
<p>When you have achieved isolation running in your existing Rails monolith you can simply promote this Rails Engine into a full-size Rails app by creating a parent dummy app and that can be deployed to Lamby</p>
<p>If you have to rollback because you need to tweak Lambda's performance or it wasn't the right fit it's easy to do so.</p>
<p>Generally, boot time should be a non-issue. And if it were, AWS has <a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-concurrency.html">provisioned currency</a></p>
<h3 id="but-what-happens-to-puma">But what happens to Puma?</h3>
<p>You don't need a ruby web-server. Puma is just gone. You don't have to worry about managing a cluster of instances. Each Lambda is a single process of Rails. You're not thinking about multi-threading. It's dead simple.</p>
<p>That to me was the wow moment with Lamby. I never want to deal with debugging a Puma daemon ever again. </p>
<h3 id="but-what-about-cold-starts">But what about cold-starts?</h3>
<p>Not as problematic as you might expect. You'll encounter cold-starts less frequently because you have a Lambda that is handling multiple routes keeping your lambda quite warm. </p>
<p>I was astonished at how fast the sample Lamby app was without Provisioned Concurrency.</p>
<p>I think I plan to use Provisioned Concurrency so I'll have to report back on cost and if I really need to use it or not.</p>
<h3 id="but-how-am-i-supposed-to-use-an-application-performance-monitoring-apm-for-a-rails-app-running-in-a-lambda">But how am I supposed to use an Application Performance Monitoring (APM) for a Rails app running in a Lambda?</h3>
<p>I think NewRelic's APM can run on Lambda.</p>
<p>I am not certain about DataDog or RayGun, Skylight or other APM providers.</p>
<p>I would think the hurdle is how is the APM agent going to report back to their respected service.</p>
<p>Ken is used to using New Relic but is considering <a target="_blank" href="https://dev.to/metaskills/comment/17e8o">just utilizing CloudWatch</a> and again Ken has tutorialized how we can gain observability using <a target="_blank" href="https://lamby.custominktech.com/docs/logging_metrics_observability">CloudWatch Dashboards, CloudWatch Embedded Metric, CloudWatch Log Insights </a> </p>
<p>For some not being able to use the DataDog APM could prove a deal-breaker because when you're in an eco-system like DataDog where you want to correlate APM traces to SLOs you then have to figure how how to construct all of that yourself on AWS.</p>
<p>CloudWatch Log Insights is truly revolutionary so I think if you're growing up with Lamby and Log Insights you'll find it cost-effective and convenient. </p>
<h3 id="but-what-happens-when-lambda-decides-to-release-to-deprecate-an-old-ruby-version-and-i-have-to-upgrade-to-a-new-one">But what happens when Lambda decides to release to deprecate an old Ruby version and I have to upgrade to a new one?</h3>
<p>Then you have to upgrade your Rails app and ideally, that is what you're supposed to be doing all along with your Monolith running on VM anyway. AWS actually sells this as a security feature where they are forcing you into best practices. </p>
<p>This could be worrisome if your Rails app relies on gems that convenient and deeply rooted in your system but then one day the maintainers stop supporting their gem and keeping it current.</p>
<p>This is a current problem for me on one of my existing Rails app where I can't upgrade because of Paperclip and I'm stuck on Rails 4. So it requires major uplift, but honestly, that is on me for relying on many gems that provide a nice DSL instead of just choosing a less DRY but simpler solution.</p>
<h3 id="but-how-i-am-suppose-to-rails-console-to-live-debug-my-server">But how I am suppose to Rails Console to live-debug my server?</h3>
<p>You can't as far as I'm aware of. It is possible with <a target="_blank" href="https://github.com/mnc/rails-console-ecs">Rails Console on ECS</a></p>
<p>It's not good practice to be live debugging your server anyway. </p>
<h3 id="some-parting-resources">Some parting resources</h3>
<p>Just a few links for you to explore on your own:</p>
<ul>
<li>https://technology.customink.com/blog/2020/03/13/using-aws-sam-cookiecutter-project-templates-to-kickstart-your-ambda-projects/</li>
<li>https://dev.to/aws-heroes/aws-lambda-microservice-workshop-using-s3-libvips-ruby-4o96</li>
<li>https://github.com/customink/aws-embedded-metrics-customink</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Welcoming Antonio Lo Fiego to the Core-Team]]></title><description><![CDATA[We are very excited to welcome Antonio Lo Fiego to the #100DaysofCloud Core-Team 🎉
What is #100DaysOfCloud?
#100DaysOfCloud is an open source community, inspired by Alex Kallaway's #100DaysofX challenges. This community strives to provide both techn...]]></description><link>https://blog.100daysofcloud.com/welcoming-antonio-lo-fiego-to-the-core-team</link><guid isPermaLink="true">https://blog.100daysofcloud.com/welcoming-antonio-lo-fiego-to-the-core-team</guid><category><![CDATA[AWS]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[100DaysOfCloud]]></category><dc:creator><![CDATA[Christina]]></dc:creator><pubDate>Thu, 27 Aug 2020 14:19:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1598537883474/CZM-FD3ex.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We are very excited to welcome Antonio Lo Fiego to the #100DaysofCloud Core-Team 🎉</p>
<h2 id="what-is-100daysofcloud">What is #100DaysOfCloud?</h2>
<p>#100DaysOfCloud is an open source community, inspired by Alex Kallaway's <a target="_blank" href="https://www.100daysofx.com/">#100DaysofX challenges</a>. This community strives to provide both technical and non-technical people with tools and resources related to Cloud technologies. </p>
<p>We do this by building and fostering an accessible platform and a strong network of professionals from all levels. This holds space for journeyers to get both technical help and personal support, as well as projects to complete in order to enhance their real-world skills.</p>
<h2 id="what-is-a-core-member">What is a Core Member?</h2>
<p>Core members of the #100DaysofCloud lead the direction of the project, contribute to its overall infrastructure and work to create a diverse and inclusive community. Core members are expected to attend Core meetings and create actionable goals in a defined area of the community and meet them each quarter. </p>
<p>As a team, we have defined prerequisites we consider before inviting someone to join the Core Team. <strong>These are:</strong></p>
<ul>
<li>90 days of activity</li>
<li>Doing a "<strong>big give</strong>"</li>
<li>Accept terms of service</li>
<li>Demonstrate inclusion, diversity, and friendliness</li>
</ul>
<h2 id="why-join-core">Why join Core?</h2>
<p>Joining Core is an opportunity to have a consistent contribution to a community that we all find useful and welcoming.</p>
<p>It's also a chance to shape new ways to provide accessibility, encouragement, and community to the cloud computing space for an ever-growing number of people (900+ Journeyers in the Discord!).</p>
<h2 id="who-is-antonio">Who is Antonio?</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1598468245047/5aM6UvzRD.jpeg" alt="Portrait of Antoni" /></p>
<p>Antonio is a Data Scientist turned Cloud Engineer. </p>
<p>Even though most of his education is in business-oriented disciplines, he found himself building software in every step of his career. When the tools learned in school were not enough to solve a task, he turned his eye to online learning and hands-on experimentation. These forays led him to learn cool technologies like Docker, React, Python, Node.js, and others. </p>
<p>He ultimately fell in love with the AWS world after having to unsuccessfully tinker with it during an internship. He is now a <strong>Certified Solutions Architect</strong>, living and breathing everything AWS and focused on making Cloud literacy accessible to everyone, especially to people coming from non-technical backgrounds.</p>
<h2 id="what-did-antonio-do-to-achieve-core-status">What did Antonio do to achieve <strong>Core</strong> status?</h2>
<p>These are some of his many contributions/achievements: </p>
<ul>
<li>He has contributed to <a target="_blank" href="https://github.com/100DaysOfCloud/100DaysOfCloudIdeas">100DaysOfCloudIdeas</a> and the documentation and structure of our open-source projects.</li>
<li>He organized volunteers on his own, and moved community goals forward</li>
<li>He architected and spearheaded the development for the frontend of the new platform</li>
<li>He designed the platform's serverless backend architecture, including the progress algorithm</li>
<li>He's done Developer Advocacy for #100DaysOfCloud on various social media platforms</li>
<li>He's has had continuous active presence in the <a target="_blank" href="https://discord.gg/">Discord</a></li>
<li>He's volunteered his time to help others with their cloud problems</li>
</ul>
<h2 id="where-i-can-find-antonio">Where I can find Antonio?</h2>
<p>You can learn more about Antonio and follow him here:</p>
<ul>
<li><a target="_blank" href="https://twitter.com/antonio_lofiego">Twitter</a></li>
<li><a target="_blank" href="https://blog.antoniolofiego.com/">Personal Blog</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/antoniolofiego/">LinkedIn</a></li>
<li><a target="_blank" href="https://github.com/antoniolofiego">Github</a></li>
</ul>
<p>The #100DaysofCloud Core Team is continuing to expand along with our community. We are working to create a balanced, diverse, and inclusive team. We will continue to encourage and identify members in our community who we believe meet the prerequisites we have defined.
We are excited to have Antonio join us on the Core Team to help us all continue to grow!</p>
]]></content:encoded></item><item><title><![CDATA[An atomic, incremental counter for DynamoDB with Lambda 🎟]]></title><description><![CDATA[Photo by  Pasternak 
This is a post on how to atomically increment a value in a DynamoDB item with Python and Lambda. Let's keep it short and sweet, you're only looking for the code anyways😈
Permissions
Your Lambda function's role needs to have perm...]]></description><link>https://blog.100daysofcloud.com/an-atomic-incremental-counter-for-dynamodb-with-lambda</link><guid isPermaLink="true">https://blog.100daysofcloud.com/an-atomic-incremental-counter-for-dynamodb-with-lambda</guid><category><![CDATA[AWS]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[lambda]]></category><category><![CDATA[DynamoDB]]></category><dc:creator><![CDATA[Chris Nagy]]></dc:creator><pubDate>Thu, 13 Aug 2020 18:30:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1597343233209/vgBHObJwv.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Photo by  <a target='_blank' rel='noopener'  href="https://www.deviantart.com/pasternak">Pasternak</a> </em></p>
<p>This is a post on how to atomically increment a value in a DynamoDB item with Python and Lambda. Let&#39;s keep it short and sweet, you&#39;re only looking for the code anyways😈</p>
<h3 id="permissions">Permissions</h3>
<p>Your Lambda function&#39;s role needs to have permissions to the DynamoDB table you want to work with. These are the permissions that you need for this exact situation.</p>
<pre><code class="lang-{yaml}">- Effect: Allow
  Action:
      - dynamodb:GetItem
      - dynamodb:PutItem
      - dynamodb:UpdateItem
  Resource: &quot;arn of your DyanamoDB table&quot;
</code></pre>
<h3 id="lambda">Lambda</h3>
<p>This Lambda is written in Python 3.8. Make sure to have it assume the role that contains the above IAM permissions. This is the table and the item:</p>
<pre><code><span class="hljs-attribute">TableName</span>: my_dynamo_table_name
<span class="hljs-attribute">PrimaryKey</span>: user1
<span class="hljs-attribute">Attribute</span>: clicks
</code></pre><p>And here is the Lambda function&#39;s code first. I&#39;ll explain in detail down below.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> boto3

<span class="hljs-comment"># Initialize dynamodb boto3 object</span>
dynamodb = boto3.resource(<span class="hljs-string">'dynamodb'</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span><span class="hljs-params">(event, context)</span>:</span>

    <span class="hljs-comment"># Set up dynamodb table object</span>
    table = dynamodb.Table(<span class="hljs-string">'my_dynamo_table_name'</span>)

    <span class="hljs-comment"># Atomic update an item in table or add if doesn't exist</span>
    ddbResponse = table.update_item(
        Key={
            <span class="hljs-string">"id"</span>: <span class="hljs-string">"user1"</span>
        },
        UpdateExpression=<span class="hljs-string">'ADD clicks :inc'</span>,
        ExpressionAttributeValues={
            <span class="hljs-string">':inc'</span>: <span class="hljs-number">1</span>
        },
        ReturnValues=<span class="hljs-string">"UPDATED_NEW"</span>
    )

    <span class="hljs-comment"># Return dynamodb response object</span>
    print(ddbResponse)
    <span class="hljs-keyword">return</span> ddbResponse
</code></pre>
<h3 id="breaking-it-down">Breaking it down</h3>
<ul>
<li><code>import json</code> &amp; <code>import boto3</code> are for importing the necessary libraries.</li>
<li><code>dynamodb = boto3.resource(&#39;dynamodb&#39;)</code> initializes a DynamoDB boto3 object (outside of the function)</li>
<li><code>table = dynamodb.Table(&#39;my_dynamo_table_name&#39;)</code> initializes the &quot;connection&quot; to the table called <code>my_dynamo_table_name</code> and puts it into the <code>table</code> object.</li>
<li><strong><code>ddbResponse = table.update_item(...</code> is the key here!</strong> This is the part where all communication with DyanmoDB happens.<ul>
<li><code>Key={&quot;id&quot;: &quot;user1&quot;},</code> is referring to the item&#39;s primary key. As you&#39;ve seen above, this table&#39;s PK is <code>id</code>, and since this item doesn&#39;t exist yet (because I haven&#39;t created it in the table, nor will I have to), I&#39;ll just call its PK <code>user1</code>.</li>
<li><code>UpdateExpression=&#39;ADD clicks :inc&#39;,</code> will tell DynamoDB how to update the item from above. That <code>ADD</code> is very important, as it could be <code>SET</code> or <code>REMOVE</code>. <code>ADD</code> means that instead of just setting an elready existing attribute&#39;s value, this can also create the item or the attribute if it doesn&#39;t exist. This is crucial because you don&#39;t want to manually have to create every attribute and item that&#39;s in your table. The next part is <code>clicks :inc</code> where <code>clicks</code> is the name of an/the attribute of your item.</li>
<li><code>ExpressionAttributeValues={ &#39;:inc&#39;: 1 },</code> defines that <code>:inc</code> value, it can be called anything, it just hast to match with the above line. This is where you can set <em>how much</em> you want to increment the value every time the function runs, we&#39;ll stick with one.</li>
<li><code>ReturnValues=&quot;UPDATED_NEW&quot;</code> defines what will be returned by the <code>update_item</code> operation. We only care about the finished value so we&#39;ll keep it at this.</li>
</ul>
</li>
</ul>
<p>And that&#39;s it basically! After that it prints the response from the DynamoDB operation and you can see that the item and it&#39;s attribute got created and then incrementally updated in a single operation (atomically). If the item exists already, it will only increment its value and not recreate it.</p>
<h3 id="test">Test</h3>
<p>You can test it right in the console. Make sure that you created the DynamoDB table however (no need to create the item). Then just &quot;Test&quot; the Lambda in the console and you should see something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1597341195639/BjYXyL_g5.png" alt="Screen Shot 2020-08-13 at 19.24.42.png"></p>
<h3 id="yo-let-s-connect">Yo, let&#39;s connect</h3>
<p>Check me out on Twitter -  <a target='_blank' rel='noopener'  href="https://twitter.com/chris_the_nagy">Chris Nagy</a>  - if you have any questions or just wanna chat😉</p>
]]></content:encoded></item><item><title><![CDATA[Hello Cloud 🌤]]></title><description><![CDATA[What a wonderful day to learn cloud computing.]]></description><link>https://blog.100daysofcloud.com/hello-cloud</link><guid isPermaLink="true">https://blog.100daysofcloud.com/hello-cloud</guid><dc:creator><![CDATA[Andrew Brown]]></dc:creator><pubDate>Fri, 07 Aug 2020 16:31:16 GMT</pubDate><content:encoded><![CDATA[<p>What a wonderful day to learn cloud computing.</p>
]]></content:encoded></item><item><title><![CDATA[Test article]]></title><description><![CDATA[Test body of article]]></description><link>https://blog.100daysofcloud.com/test-article</link><guid isPermaLink="true">https://blog.100daysofcloud.com/test-article</guid><dc:creator><![CDATA[Antonio Lo Fiego (He/Him)]]></dc:creator><pubDate>Fri, 07 Aug 2020 16:28:08 GMT</pubDate><content:encoded><![CDATA[<p>Test body of article</p>
]]></content:encoded></item></channel></rss>