<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mikkel</title>
    <description>The latest articles on DEV Community by Mikkel (@miksimal).</description>
    <link>https://dev.to/miksimal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F374554%2F5695448a-d3f3-48c8-be68-2a736018cb59.jpeg</url>
      <title>DEV Community: Mikkel</title>
      <link>https://dev.to/miksimal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/miksimal"/>
    <language>en</language>
    <item>
      <title>Resources, projects, and lessons from 100 days of (serverless) cloud learning</title>
      <dc:creator>Mikkel</dc:creator>
      <pubDate>Sun, 25 Oct 2020 13:19:15 +0000</pubDate>
      <link>https://dev.to/miksimal/resources-projects-and-lessons-from-100-days-of-serverless-cloud-learning-ekb</link>
      <guid>https://dev.to/miksimal/resources-projects-and-lessons-from-100-days-of-serverless-cloud-learning-ekb</guid>
      <description>&lt;p&gt;I recently completed my challenge of spending at least 1 hour a day learning about (serverless) cloud for 100 days &lt;i&gt;almost&lt;/i&gt;* in a row.&lt;/p&gt;

&lt;p&gt;In this post, I summarise the main topics I covered and the best resources for each, the projects I built, and some lessons for future self-directed learning projects.&lt;/p&gt;

&lt;p&gt;*It took me 114 days - with a pandemic and some vacation time thrown in, that's close enough I think!&lt;/p&gt;

&lt;h2&gt;
  
  
  Topics and resources
&lt;/h2&gt;

&lt;p&gt;The topics that I spent the most time on and found the best resources for were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Fundamentals and the serverless landscape&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data modelling with DynamoDB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Best practices and design patterns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The business case for 'serviceful serverless'&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Fundamentals and the serverless landscape&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Despite having an AWS SAA cert and having used AWS at work before the challenge, a key focus for me was gaining a stronger foundation by really understanding the serverless landscape, including tools, best practices, and design patterns.&lt;/p&gt;

&lt;p&gt;For this, my favourites were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://offbynone.io/"&gt;Off-by-none&lt;/a&gt;: Jeremy Daly's weekly serverless newsletter. More resources than you have time to read, but always some great stuff for all levels in there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://serverless-stack.com/"&gt;Serverless Stack&lt;/a&gt;: an excellent, free resource that gets you going with building a full-stack project using React and Serverless Framework. Having an opinionated guide with links to further best practices and little tricks (like &lt;a href="https://serverless-stack.com/chapters/mapping-cognito-identity-id-and-user-pool-id.html"&gt;this one&lt;/a&gt;) was a life-saver.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/aws-samples/aws-serverless-airline-booking"&gt;Serverless Airline Booking&lt;/a&gt; and its accompanying &lt;a href="https://github.com/aws-samples/aws-serverless-airline-booking/tree/twitch"&gt;Twitch streams&lt;/a&gt; (14+ hours of content). Covers a lot of ground despite not assuming much prior knowledge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://theburningmonk.com/2020/03/announcing-the-new-real-world-serverless-podcast/"&gt;Real-World Serverless&lt;/a&gt;: no fluff podcast that goes in-depth on best practices, architectures, and more. I have listened to &lt;em&gt;all&lt;/em&gt; the episodes until now.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Data modelling with DynamoDB&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I spent a good chunk of days on NoSQL data modelling with DynamoDB using various resources and &lt;a href="https://twitter.com/miksimal/status/1305171665991725059?s=20"&gt;applying the lessons&lt;/a&gt; to my Virtual Watercooler project.&lt;/p&gt;

&lt;p&gt;My favourite resources were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.serverlesschats.com/44/"&gt;This episode&lt;/a&gt; of Serverless Chats (also a great podcast) with Alex DeBrie.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Alex DeBrie's &lt;a href="https://youtu.be/DIQVJqiSUkE"&gt;re:Invent talk&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once I'd watched Alex's talk, I graduated to &lt;a href="https://youtu.be/6yqfmXiZTlM"&gt;Rick Houlihan's talk&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After the talks, I read Alex DeBrie's excellent and well-named book, &lt;a href="https://www.dynamodbbook.com/"&gt;&lt;em&gt;The DynamoDB Book&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.settingup.html"&gt;NoSQL Workbench&lt;/a&gt; was an excellent thinking tool when applying the lessons.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Best practices and design patterns&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Due to the &lt;a href="https://www.youtube.com/watch?v=BtJAsvJOlhM"&gt;dizzying&lt;/a&gt; and constantly increasing number of serverless building blocks, having good examples and understanding best practices to combine them into solid microservices within solid overall architectures is essential.&lt;/p&gt;

&lt;p&gt;My favourite resources were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Jeremy Daly's summary of &lt;a href="https://www.jeremydaly.com/serverless-microservice-patterns-for-aws/"&gt;19 serverless Microservice Patterns&lt;/a&gt;. Excellent reference. Explains e.g. the 'Frugal Consumer' and 'Scalable Webhook' patterns used in my Virtual Watercooler project to cope with a maximum emails-per-second limit for SES.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://d1.awsstatic.com/whitepapers/architecture/AWS-Serverless-Applications-Lens.pdf"&gt;Serverless Application Lens&lt;/a&gt;. Quite readable paper from AWS giving detail on various serverless architectures and considerations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The intro and linked video in &lt;a href="https://theburningmonk.com/2020/07/how-i-scaled-an-appsync-project-to-200-resolvers/"&gt;this article&lt;/a&gt; by Yan Cui. Gave me the idea to use Algolia as a 'serverless ElasticSearch' and DynamoDB Streams to trigger a Lambda that synchs changes to it in my Sentimental Robot project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;These episodes of the 'Real World Serverless' podcast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Episode &lt;a href="https://www.buzzsprout.com/877747/3693316-15-serverless-at-irobot-with-ben-kehoe"&gt;15&lt;/a&gt; with Ben Kahoe from iRobot.&lt;/li&gt;
&lt;li&gt;Episode &lt;a href="https://www.buzzsprout.com/877747/3797261-17-findev-and-wardley-mapping-with-aleksandar-simovic-and-slobodan-stojanovic"&gt;18&lt;/a&gt; (with Aleksandar Simovic and Slobodan Stojanovic) and &lt;a href="https://www.buzzsprout.com/877747/4218644-22-real-world-serverless-with-gojko-adzic"&gt;22&lt;/a&gt; (with Gojko Adzic) contain good discussion of hexagonal architecture (Ports &amp;amp; Adapters).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The business case for 'serviceful serverless'&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Spending so much time on serverless while working in a very 'servermore' (?) environment at work prompted a lot of thinking about this topic. I've got a ton of notes, so will have to write about it soon.&lt;/p&gt;

&lt;p&gt;Some great resources are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Joe Emison's &lt;a href="https://www.infoq.com/articles/serverless-sea-change/"&gt;The Serverless Sea Change&lt;/a&gt;. Coining the term 'Serviceful Serverless'.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;These episodes of the 'Real World Serverless' podcast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Episodes &lt;a href="https://www.buzzsprout.com/877747/2870908-2-the-case-for-monorepoes-with-joe-emison"&gt;2&lt;/a&gt; and &lt;a href="https://www.buzzsprout.com/877747/2871130-3-building-a-fully-serverless-insurance-company-with-joe-emison"&gt;3&lt;/a&gt; with Joe Emison.&lt;/li&gt;
&lt;li&gt;Episode &lt;a href="https://www.buzzsprout.com/877747/3824681-18-hexagonal-architecture-and-voice-with-aleksandar-simovic-and-slobodan-stojanovic"&gt;17&lt;/a&gt; with Aleksandar Simovic and Slobodan Stojanovic talking about e.g. 'FinDev' and Wardley Maps.&lt;/li&gt;
&lt;li&gt;Episode &lt;a href="https://www.buzzsprout.com/877747/4615988-24-serverless-at-stedi-with-zack-kanter?play=true"&gt;24&lt;/a&gt; with Zack Kanter from Stedi.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some good discussion about serverless for start-ups between Nader Dabit and Tom McLaughlin in these three threads: &lt;a href="https://twitter.com/dabit3/status/1282002976836550657?s=20"&gt;one&lt;/a&gt;, &lt;a href="https://twitter.com/tmclaughbos/status/1282028617078386688?s=20"&gt;two&lt;/a&gt;, &lt;a href="https://twitter.com/tmclaughbos/status/1282084385701998592?s=20"&gt;three&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://twitter.com/joeemison/status/1298960701097226240?s=21"&gt;This&lt;/a&gt; was a great thread to illustrate both (a) a ridiculously small AWS bill and (b) the internet (Alex DeBrie) helping make it even smaller.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www2.eecs.berkeley.edu/Pubs/TechRpts/2019/EECS-2019-3.html"&gt;Cloud Programming Simplified: A Berkeley View on Serverless Computing&lt;/a&gt;. 35 page paper with the bold claim that serverless 'represents an evolution that parallels the transition from assembly language to high-level programming languages'.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Projects
&lt;/h2&gt;

&lt;p&gt;The majority of my learning was structured around the following projects. Whenever I learnt something new, I would go back and apply it to these.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;a href="https://virtualwatercooler.xyz/"&gt;Virtual Watercooler&lt;/a&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Allows you to create an 'organisation', add members to it, and then randomly pair members for coffee chats - either by clicking a button or by choosing a frequency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend tech stack:&lt;/strong&gt; DynamoDB (single-table design), API Gateway, Cognito, Lambda (Javascript), SQS, SES, Cloudwatch, s3, and CloudFront. All packaged up with Serverless Framework.&lt;/p&gt;

&lt;p&gt;Code available &lt;a href="https://github.com/miksimal/virtualwatercooler-backend"&gt;here&lt;/a&gt;. Wrote &lt;a href="https://dev.to/miksimal/how-to-dynamically-create-cloudwatch-rules-to-let-users-schedule-recurring-actions-2php"&gt;this&lt;/a&gt; to help me confirm that my approach to letting admins schedule recurring chats made sense.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;a href="https://sentimentalrobot.miksimal.com/"&gt;Sentimental Robot&lt;/a&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Scrapes BBC news headlines each morning, runs them through sentiment analysis, and allows the user to search through headlines and sentiments, or just get an overview of the past few days' sentiment. Had already started this but built a lot on top during the challenge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend tech stack:&lt;/strong&gt; Algolia, DynamoDB and DynamoDB Streams, Comprehend, CloudWatch, API Gateway, Lambda (Javascript), s3, CloudFront. All except Algolia packaged up with Serverless Framework.&lt;/p&gt;

&lt;p&gt;Code available &lt;a href="https://github.com/miksimal/sentimental-robot-backend"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;a href="https://forgotmyglasses.miksimal.com/"&gt;ForgotMyGlasses&lt;/a&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Tend to forget your glasses? No problem, teach the robot what your friends look like and use it to help you recognise them in the wild.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend tech stack:&lt;/strong&gt; Rekognition, Lambda (Javascript), Cognito Identity Pool (unauthenticated role), and Cloudwatch. Using Serverless Framework as always.&lt;/p&gt;

&lt;p&gt;Code available &lt;a href="https://github.com/miksimal/forgotmyglasses"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;SimpleQuiz (in progress)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Started this right at the end of the challenge. Decided on the data model and got a basic &lt;a href="https://twitter.com/miksimal/status/1318299480773582849?s=20"&gt;real-time connection working with subscriptions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend tech stack:&lt;/strong&gt; AppSync (GraphQL), DynamoDB, Lambda (Javascript), Cognito Identity Pool (unauthenticated role), and a few more probably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons for future learning projects
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Open-ended 'benchmark projects'&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For my learning, I like to define what Scott Young calls &lt;a href="https://www.scotthyoung.com/blog/2019/11/15/drill-or-benchmark/"&gt;'benchmark projects'&lt;/a&gt;, where you pick something that you cannot do right now, but you &lt;em&gt;might&lt;/em&gt; be able to do if you improved your skills, e.g. earning a difficult cloud certification.&lt;/p&gt;

&lt;p&gt;This strategy worked well for me for the challenge. Had I tried to define specific topics and learning objectives at the start, I'd have failed as I simply did not have enough of a foundation to do so. The learning objectives I'd have set on Day 0, would have hindered rather than helped me on Day 20.&lt;/p&gt;

&lt;p&gt;Had I set a more concrete goal or planned my journey in more detail at the start, for example, I'd never have spent the 30 or so hours on NoSQL data modelling that I enjoyed so much.&lt;/p&gt;

&lt;p&gt;Instead, I set the goal of building 2 - 3 relatively 'production-ready' fully serverless projects and let that guide my learning. On day one I started building, and then discovered the missing pieces (such as NoSQL data modelling) I needed to fill in.&lt;/p&gt;

&lt;p&gt;I wasn't interested in earning another certification (there aren't any fully serverless ones), but I think that would've been an excellent way to guide learning as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Learning vs. reporting&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;At the start, I stuck diligently to reporting progress and learning every single day. Gradually, I ran into two problems, however. Firstly, as topics got more advanced, I did not always have much to report from a day's worth of learning. Additionally, some days I only had 60 minutes available, so reporting would steal away scarce learning time (with the potential to get distracted on Twitter along the way).&lt;/p&gt;

&lt;p&gt;In short, I started having more fun and being more effective once I switched to reporting by progress rather than daily. Sometimes that meant grouping 2 - 3 days, sometimes it was daily. But fitting reporting around the learning rather than the other way around is how I'd approach future projects like this.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Becoming 'active on the internet'&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Since I stopped playing World of Warcraft and Counter Strike many years ago, I'd become mostly an 'observer' on the internet.&lt;/p&gt;

&lt;p&gt;Seeing the 100 Days of Cloud community grow on Twitter and Discord, frequently reporting progress on Twitter, and following other people's learning challenges was super rewarding and got me back into being 'active on the internet'.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.swyx.io/learn-in-public/"&gt;'Learning in public'&lt;/a&gt; like this is extremely powerful, making for better learning, networking, and accountability all in one. For my future projects, I'd need a very good excuse &lt;em&gt;not&lt;/em&gt; to build or learn 'in public'.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback or questions?
&lt;/h2&gt;

&lt;p&gt;I'd love to know if you found this helpful, got any questions I might be able to help with on your cloud journey, or have feedback for me. &lt;a href="https://twitter.com/miksimal"&gt;Twitter&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/mikkel-lauritzen/"&gt;Linkedin&lt;/a&gt; are the best places to connect.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>100daysofcloud</category>
    </item>
    <item>
      <title>How to dynamically create CloudWatch Rules to let users schedule recurring actions</title>
      <dc:creator>Mikkel</dc:creator>
      <pubDate>Tue, 28 Jul 2020 08:25:51 +0000</pubDate>
      <link>https://dev.to/miksimal/how-to-dynamically-create-cloudwatch-rules-to-let-users-schedule-recurring-actions-2php</link>
      <guid>https://dev.to/miksimal/how-to-dynamically-create-cloudwatch-rules-to-let-users-schedule-recurring-actions-2php</guid>
      <description>&lt;p&gt;I recently needed to build a feature to let users set their own frequency for a recurring action. Specifically, I wanted to let the user decide how often my app should randomly pair members of their organisation and connect them via email.&lt;/p&gt;

&lt;p&gt;I couldn't find many resources on how to easily accomplish this with serverless on AWS, so thought I'd share my learnings and explain the approach I took.&lt;/p&gt;

&lt;p&gt;In short, I have an API Gateway / Lambda endpoint which both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Adds or updates a CloudWatch Rule with the desired frequency. This rule will then trigger a Lambda (when it's time) which pairs and emails organisation members.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adds or updates a 'RecurrenceRule' DynamoDB item (I store this here to make it easy to display to the user what their current setting is).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Visually, it could look e.g. like this with a simple dropdown:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiksimal.com%2Fassets%2Fend-result-set-frequency.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiksimal.com%2Fassets%2Fend-result-set-frequency.gif" alt="The end result: selecting a frequency updates a CW rule"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;In this post, I'll explain how I implemented the backend side of this, using The Serverless Framework, API Gateway, Lambda, and CloudWatch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; there may very well be better ways to achieve this. In fact, I'm secretly hoping that by sharing my approach here I will get some feedback to improve my solution, so please reach out if you've got ideas!&lt;/p&gt;



&lt;h3&gt;
  
  
  Write a 'ManageRecurrenceRules' Lambda
&lt;/h3&gt;

&lt;p&gt;First, write a Lambda function which manages the recurrence rules. This is the one that will be triggered by users clicking in the dropdown example above.&lt;/p&gt;

&lt;p&gt;We will need the clients for both DynamoDB and CloudWatch as well as an environmental variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamoDb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DynamoDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DocumentClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cloudWatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CloudWatchEvents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recurringPairAndEmailFunctionArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RECURRING_PAIRANDEMAIL_LAMBDA_ARN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ignoring various other things you may want to do (e.g. I retrieve an organisationId from Cognito), we first need to construct a cron expression based on the POST request, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frequency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;scheduleExpression&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frequency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fridays&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;scheduleExpression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cron(0 12 ? * FRI *)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Every 5 Minutes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;scheduleExpression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cron(0/5 * * * ? *)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Daily&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;scheduleExpression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cron(0 12 * * ? *)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;// return an 'Invalid frequency' error to the user;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to construct a unique rulename. I used an organisationId (which I get from an &lt;code&gt;adminGetUser&lt;/code&gt; Cognito request) combined with the stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ruleName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;organisationId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STAGE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;putRule&lt;/code&gt; to create or update the rule and then use &lt;code&gt;putTargets&lt;/code&gt; to add the desired target Lambda we want the rule to trigger in my case a Lambda function that pairs organisation members and email-intros each pair:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cloudWatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putRule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ruleName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ScheduleExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;scheduleExpression&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cloudWatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putTargets&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ruleName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;organisationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;Arn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recurringPairAndEmailFunctionArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;organisationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;organisationId&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;// the rule will pass this Input to the Lambda when it triggers it&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that I tell the rule to pass a custom Input to the Lambda target. This means that the target, in my case the 'recurringPairAndEmailFunction', can access the property directly from the event like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orgId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;organisationId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, still in the same 'try/catch' block, I add this rule to DynamoDB to easily display the currently selected frequency to the user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;USERS_TABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;organisationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;SK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RecurrenceRule&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;frequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frequency&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dynamoDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all this goes well, we return a lovely 200 OK to the user 🎉&lt;/p&gt;



&lt;h3&gt;
  
  
  serverless.yml setup
&lt;/h3&gt;

&lt;p&gt;Next, we need to define the API Gateway endpoint and pass the environmental variable to the Lambda (the &lt;code&gt;recurringPairAndEmailFunctionArn&lt;/code&gt; used above). In the serverless.yml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;manageRecurrenceRules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manageRecurrenceRules.main&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;RECURRING_PAIRANDEMAIL_LAMBDA_ARN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:lambda:${self:provider.region}:accountidgoeshere:function:${self:service}-${self:custom.stage}-recurringPairAndEmail&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rules&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;post&lt;/span&gt;
          &lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;authorizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws_iam&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the target Lambda, recurringPairAndEmail, is defined in the template as simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;recurringPairAndEmail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;recurringPairAndEmail.main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to make sure that our Rules have the required permissions to invoke our Lambda. The below allows all rules for my account in this region to invoke the Lambda (which is probably too permissive):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;RecurringPairAndEmailInvokePermission&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Lambda::Permission&lt;/span&gt;
        &lt;span class="na"&gt;DependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RecurringPairAndEmailLambdaFunction&lt;/span&gt;
        &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lambda:InvokeFunction&lt;/span&gt;
          &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;events.amazonaws.com&lt;/span&gt;
          &lt;span class="na"&gt;SourceArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:events:${self:provider.region}:accountidgoeshere:rule/*"&lt;/span&gt;
          &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:lambda:${self:provider.region}:accountidgoeshere:function:${self:service}-${self:custom.stage}-recurringPairAndEmail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we need to ensure that our 'ManageRecurrenceRules' Lambda has permissions to add rules and targets, so we add the below in the &lt;code&gt;provider&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;iamRoleStatements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Allow"&lt;/span&gt;
      &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;events:putRule&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;events:putTargets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h3&gt;
  
  
  Aaand, that's it!
&lt;/h3&gt;

&lt;p&gt;Our users can now choose between Fridays, Daily or Every 5 Minutes as frequencies by making a POST request to the /rules path.&lt;/p&gt;

&lt;p&gt;In CloudWatch, a rule will look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiksimal.com%2Fassets%2Fthe-result-in-cloudwatch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiksimal.com%2Fassets%2Fthe-result-in-cloudwatch.png" alt="the resulting rule in CloudWatch for emails every Friday"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h3&gt;
  
  
  How can the solution be improved? Is this the 'right' way?
&lt;/h3&gt;

&lt;p&gt;Now, back to my secret intention with this post... How can the solution be improved? Is this the 'right' way of implementing this feature?&lt;/p&gt;

&lt;p&gt;How can I best work around the constraints on CloudWatch Rules, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The per-region &lt;em&gt;soft&lt;/em&gt; limit of 100 CW Rules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The limit of 5 targets per rule (e.g. so having a rule for each frequency and adding organisations as Input probably isn't the way either).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Thanks for reading and I hope to hear from you either here or on Twitter (&lt;a href="https://twitter.com/miksimal" rel="noopener noreferrer"&gt;@miksimal&lt;/a&gt;)!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
