<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tomek on Software</title>
    <description>Software - shaken, not stirred
</description>
    <link>https://tomasz.janczuk.org/</link>
    <atom:link href="https://tomasz.janczuk.org/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Wed, 06 Nov 2024 16:10:10 +0000</pubDate>
    <lastBuildDate>Wed, 06 Nov 2024 16:10:10 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>StopGHG - A Free Carbon Footprint Calculator</title>
        <description>&lt;p&gt;Lately I’ve been exploring the climate space with an eye toward areas where technology can help companies do their part to protect it. One such area is carbon footprint calculation. As a result, today we are shipping &lt;a href=&quot;https://go.stopghg.com/P4l2ZG&quot;&gt;StopGHG&lt;/a&gt; - a free, self-service carbon footprint calculator for companies.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2024-05-20/dali-smoking-stacks.jpeg&quot; class=&quot;tj-img-diagram-50&quot; alt=&quot;StopGHG - Free Carbon Footprint Calculator&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Understanding your company’s carbon footprint is indispensable to remain competitive. Many regulatory frameworks, such as the &lt;a href=&quot;(https://finance.ec.europa.eu/capital-markets-union-and-financial-markets/company-reporting-and-auditing/company-reporting/corporate-sustainability-reporting_en)&quot;&gt;CSRD directive in the European Union&lt;/a&gt; or the &lt;a href=&quot;https://www.sec.gov/news/press-release/2024-31&quot;&gt;SEC Climate Rule in the US&lt;/a&gt;, require accurate carbon footprint reporting. Beyond legal requirements, companies worldwide that want to improve their image make their carbon footprint public as part of ESG (Environmental, Social, and Governance) reporting. Frequently, customers and partners ask for your carbon footprint to satisfy their own regulatory obligations.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2024-05-20/dali-machine.jpeg&quot; class=&quot;tj-img-diagram-50&quot; alt=&quot;StopGHG - darmowy kalkulator śladu węglowego&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Calculating your company’s greenhouse gas emissions is complex. &lt;a href=&quot;https://go.stopghg.com/P4l2ZG&quot;&gt;StopGHG&lt;/a&gt; uses the global standards set by the &lt;a href=&quot;https://ghgprotocol.org/&quot;&gt;Greenhouse Gas Protocol (GHG Protocol)&lt;/a&gt; and the current emission factors published by the &lt;a href=&quot;https://www.ipcc.ch/&quot;&gt;Intergovernmental Panel for Climate Change (IPCC)&lt;/a&gt; to provide you with the most accurate results needed for compliance.&lt;/p&gt;

&lt;p&gt;StopGHG is dedicated to helping the climate by providing companies like yours with the tools they need to do their part. Our free, easy-to-use, web-based carbon footprint calculator built on the GHG Protocol is a great companion on your sustainability journey.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://go.stopghg.com/P4l2ZG&quot;&gt;Start your carbon footprint calculations today!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2024-05-20/dali-earth.jpeg&quot; class=&quot;tj-img-diagram-50&quot; alt=&quot;StopGHG - darmowy kalkulator śladu węglowego&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 20 May 2024 00:00:00 +0000</pubDate>
        <link>https://tomasz.janczuk.org/2024/05/stopghg-free-carbon-footprint-calculator.html</link>
        <guid isPermaLink="true">https://tomasz.janczuk.org/2024/05/stopghg-free-carbon-footprint-calculator.html</guid>
        
        <category>stopghg</category>
        
        <category>co2</category>
        
        <category>co2e</category>
        
        <category>ghg</category>
        
        <category>esd</category>
        
        <category>csrd</category>
        
        <category>greenhouse gasses</category>
        
        <category>climate</category>
        
        
      </item>
    
      <item>
        <title>StopGHG - darmowy kalkulator śladu węglowego</title>
        <description>&lt;p&gt;Ostatnio zainteresowałem się zagadnieniami związanymi z ochroną klimatu, w szczególności obszarami, w których technologia może wesprzeć firmy w jego ochronie. Pierwszy taki obszar to wyliczenie śladu węglowego. W rezultacie, dzisiaj wypuszczamy &lt;a href=&quot;https://go.stopghg.com/Yw2ko5&quot;&gt;StopGHG&lt;/a&gt; - darmowy kalkulator śladu węglowego dla firm.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2024-05-20/dali-smoking-stacks.jpeg&quot; class=&quot;tj-img-diagram-50&quot; alt=&quot;StopGHG - darmowy kalkulator śladu węglowego&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Określenie śladu węglowego Twojej firmy jest niezbędne do opracowania strategii przewagi konkurencyjnej. Wiele ram regulacyjnych, takich jak &lt;a href=&quot;https://finance.ec.europa.eu/capital-markets-union-and-financial-markets/company-reporting-and-auditing/company-reporting/corporate-sustainability-reporting_en&quot;&gt;dyrektywa CSRD Unii Europejskiej&lt;/a&gt; czy &lt;a href=&quot;https://www.sec.gov/news/press-release/2024-31&quot;&gt;SEC Climate Rule w USA&lt;/a&gt;, wymaga od firm regularnego raportowania śladu węglowego. Pomijając wymagania prawne, firmy dążące do poprawy swojego wizerunku upubliczniają swój ślad węglowy w ramach raportów ESG (Environmental, Social, Governance). Często też klienci i partnerzy Twojej firmy oczekują danych związanych ze śladem węglowym w celu zaspokojenia własnych potrzeb raportowania.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2024-05-20/dali-machine.jpeg&quot; class=&quot;tj-img-diagram-50&quot; alt=&quot;StopGHG - darmowy kalkulator śladu węglowego&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Wyliczenie emisji gazów cieplarnianych jest skomplikowane. Dlatego zbudowaliśmy darmowy kalkulator śladu węglowego &lt;a href=&quot;https://go.stopghg.com/Yw2ko5&quot;&gt;StopGHG&lt;/a&gt;, który wykorzystuje globalne standardy zdefiniowane przez organizację &lt;a href=&quot;https://ghgprotocol.org/&quot;&gt;Greenhouse Gas Protocol (GHG Protocol)&lt;/a&gt; oraz aktualne współczynniki emisji określone przez &lt;a href=&quot;https://www.ipcc.ch/&quot;&gt;Intergovernmental Panel for Climate Change (IPCC)&lt;/a&gt; w celu dostarczenia wiarygodnych wyników zaspokajających wymagania prawne.&lt;/p&gt;

&lt;p&gt;W ramach StopGHG, naszą inspiracją są działania zmierzające do poprawy klimatu, w tym dostarczanie firmom narzędzi potrzebnych w tym procesie. Nasz bezpłatny, internetowy kalkulator śladu węglowego jest intuicyjny i łatwy w użyciu. Jest oparty na standardach GHG Protocol i stanowi doskonałe narzędzie ułatwiające drogę Twojej firmy do zrównoważonego rozwoju.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://go.stopghg.com/Yw2ko5&quot;&gt;Rozpocznij obliczenia śladu węglowego już dziś!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2024-05-20/dali-earth.jpeg&quot; class=&quot;tj-img-diagram-50&quot; alt=&quot;StopGHG - darmowy kalkulator śladu węglowego&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 20 May 2024 00:00:00 +0000</pubDate>
        <link>https://tomasz.janczuk.org/2024/05/stopghg-darmowy-kalkulator-sladu-weglowego.html</link>
        <guid isPermaLink="true">https://tomasz.janczuk.org/2024/05/stopghg-darmowy-kalkulator-sladu-weglowego.html</guid>
        
        <category>stopghg</category>
        
        <category>co2</category>
        
        <category>co2e</category>
        
        <category>ghg</category>
        
        <category>esd</category>
        
        <category>csrd</category>
        
        <category>gazy cieplarniane</category>
        
        <category>klimat</category>
        
        
      </item>
    
      <item>
        <title>Launching GetGaap - a financial API for your app or spreadsheet</title>
        <description>&lt;p&gt;Today, I am launching &lt;a href=&quot;https://getgaap.com&quot;&gt;GetGaap&lt;/a&gt;, a financial API for your app or spreadsheet. Using GetGaap, you can access data from the financial reports companies file with the Securities and Exchange Commission (SEC) in the US.&lt;/p&gt;

&lt;p&gt;GetGaap provides access to 100+ million financial facts from 18+ thousand companies filed using forms 10-K, 10-Q, and others, directly from the SEC EDGAR system. You can access quarterly or annual point-in-time data, or a quarterly or annual histogram for a specific metric.&lt;/p&gt;

&lt;p&gt;As an investor or financial researcher, you can import the data directly into a spreadsheet to build financial models, custom indices, or charts.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2024-04-08/use-from-gsheets.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Use GetGaap in Google Sheets&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As a developer, you can access the financial data using convenient HTTP APIs serving JSON or CSV. For advanced use cases, GetGaap also supports SQL access.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2024-04-08/use-from-vscode.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Use GetGaap in Node.js&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The best way to get familiar with the platform is to &lt;a href=&quot;https://getgaap.com/docs/tutorial&quot;&gt;run through the three-minute tutorial&lt;/a&gt; to build your first financial chart in Google Sheets.&lt;/p&gt;

&lt;h3 id=&quot;how-does-getgaap-work&quot;&gt;How does GetGaap work?&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.sec.gov/&quot;&gt;Securities and Exchange Commission&lt;/a&gt; (SEC) operates the &lt;a href=&quot;https://www.sec.gov/edgar/search-and-access&quot;&gt;SEC EDGAR&lt;/a&gt; system that allows companies to file their 10-K, 10-Q, and other financial statements. SEC is then required by law to make this information available to the general public. It does so by exposing a web interface as well as a set of rudimentary APIs. The APIs satisfy the requirement for public disclosure but are not intended for consumption at scale.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href=&quot;https://getgaap.com&quot;&gt;GetGaap&lt;/a&gt; comes in. It provides an API layer on top of the data exposed by SEC EDGAR that is more suitable for direct consumption from apps and spreadsheets.&lt;/p&gt;

&lt;p&gt;This is how GetGaap functions at the high level:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Every night, it imports all financial statements ever filed in SEC EDGAR as one large ZIP file containing &lt;a href=&quot;https://www.xbrl.org/&quot;&gt;xBRL&lt;/a&gt; data. This contains 100+ million financial facts from 18+ thousand companies and represents the then-current source of truth.&lt;/li&gt;
  &lt;li&gt;The large ZIP file is uploaded to AWS S3.&lt;/li&gt;
  &lt;li&gt;Processing of the ZIP file is sharded into 40 chunks, each executed in parallel in AWS Lambda.&lt;/li&gt;
  &lt;li&gt;Each shard unzips the relevant portion of the ZIP file, parses the data, and uploads it to Google BigQuery.&lt;/li&gt;
  &lt;li&gt;After all the shards have completed processing, certain static aggregates are calculated and stored in AWS S3, and the snapshot of all the data is “committed” as current.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From there, the &lt;a href=&quot;https://getgaap.com/docs&quot;&gt;HTTP API&lt;/a&gt; exposed by GetGaap runs queries in BigQuery in response to data requests.&lt;/p&gt;

&lt;p&gt;While this may sound like a system with a lot of moving parts, its implementation was hugely accelerated by building GetGaap on top of &lt;a href=&quot;https://github.com/47chapters/letsgo&quot;&gt;47chapters/letsgo&lt;/a&gt;, an OSS boilerplate architecture for AWS. It comes ready with application components like API, Website, workers, and queues, to cut months of development time.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;
</description>
        <pubDate>Mon, 08 Apr 2024 00:00:00 +0000</pubDate>
        <link>https://tomasz.janczuk.org/2024/04/launching-getgaap.html</link>
        <guid isPermaLink="true">https://tomasz.janczuk.org/2024/04/launching-getgaap.html</guid>
        
        <category>api</category>
        
        <category>getgaap</category>
        
        <category>financial</category>
        
        <category>spreadsheet</category>
        
        <category>10-k</category>
        
        <category>10-q</category>
        
        
      </item>
    
      <item>
        <title>Letsgo - a starter kit for starting startups</title>
        <description>&lt;p&gt;New SaaS businesses cut corners that are hard to fix later. Today I am shipping LetsGo, an open-source project to address this issue: &lt;a href=&quot;https://github.com/47chapters/letsgo&quot;&gt;47chapters/letsgo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When you start a new SaaS application, you often optimize for the time to market. In the process, many reduce the near-term work while creating technical debt. If your business shows signs of traction, the time comes to address this debt. However, making architectural changes with customers in production is expensive. It is like changing brakes on a running train. At one point, many early-stage SaaS businesses find themselves in a situation where it is hard to make progress and evolve the product.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2023-11-20/0.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Technical debt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The root of the problem is the lack of a proper foundation to build a new SaaS application. On the operations side, AWS provides a fantastic set of building blocks, but you still have to write a lot of code to combine them all together and engineer a proper devops experience. On the product side, many of the SaaS applications solve the same problems over and over:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;marketing site,&lt;/li&gt;
  &lt;li&gt;management dashboard,&lt;/li&gt;
  &lt;li&gt;HTTP API,&lt;/li&gt;
  &lt;li&gt;worker behind a queue,&lt;/li&gt;
  &lt;li&gt;database,&lt;/li&gt;
  &lt;li&gt;authentication,&lt;/li&gt;
  &lt;li&gt;payment processing,&lt;/li&gt;
  &lt;li&gt;devops story.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this boilerplate work stands in your way to working on the core product, so it becomes a natural target for cutting corners.&lt;/p&gt;

&lt;p&gt;This is where LetsGo comes in.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;LetsGo provides the architecture and the tooling that will put your startup on a solid foundation from day one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It helps you save months of work leading to the launch while allowing you to focus on the essence of your product. The day you let the first customer in, you have no technical debt. As you grow, you can continue focusing your resources on what matters most: your customers and product.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2023-11-20/1.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Technical debt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;LetsGo does it by providing a prescriptive architecture implemented with a modern set of technologies and robust operational tooling for managing your app in AWS.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;On day one you start with more than most startups build in the first two years.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LetsGo gives you:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;An application architecture with a &lt;strong&gt;web&lt;/strong&gt;, &lt;strong&gt;HTTP API&lt;/strong&gt;, &lt;strong&gt;worker&lt;/strong&gt;, and &lt;strong&gt;database&lt;/strong&gt; components, all wired up and ready to go.&lt;/li&gt;
  &lt;li&gt;A &lt;strong&gt;devops CLI&lt;/strong&gt; that helps you set up CI/CD and manage several deployments of your app in AWS to help you segregate your &lt;strong&gt;production and development&lt;/strong&gt; workloads or to support &lt;strong&gt;dedicated deployments&lt;/strong&gt; for your customers.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Integration with Auth0&lt;/strong&gt; to authenticate your users and protect your APIs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Integration with Stripe&lt;/strong&gt; to automate your billing and subscription lifecycle management.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Integration with Slack&lt;/strong&gt; to keep current with new customers signing up and subscriptions being paid.&lt;/li&gt;
  &lt;li&gt;A flexible &lt;strong&gt;tenancy and user model&lt;/strong&gt; with membership management and invitation flow.&lt;/li&gt;
  &lt;li&gt;A set of &lt;strong&gt;modern yet robust technologies&lt;/strong&gt; including Next.js, Node.js, and Typescript that make your days exciting and the nights boring.&lt;/li&gt;
  &lt;li&gt;LetGo is &lt;strong&gt;open source&lt;/strong&gt; under MIT, so there is no vendor lock-in and you can always see what makes it tick.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I want to welcome you to explore how LetsGo can help you at &lt;a href=&quot;https://github.com/47chapters/letsgo&quot;&gt;https://github.com/47chapters/letsgo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s go!&lt;/p&gt;
</description>
        <pubDate>Mon, 20 Nov 2023 00:00:00 +0000</pubDate>
        <link>https://tomasz.janczuk.org/2023/11/letsgo.html</link>
        <guid isPermaLink="true">https://tomasz.janczuk.org/2023/11/letsgo.html</guid>
        
        <category>letsgo</category>
        
        <category>aws</category>
        
        <category>startup</category>
        
        <category>devops</category>
        
        <category>development</category>
        
        
      </item>
    
      <item>
        <title>Create a contact sheet photo collage poster</title>
        <description>&lt;p&gt;Creating a photo collage from your digital photos should be easy, right? It is unless you have very specific needs for how you want your collage to look like.&lt;/p&gt;

&lt;p&gt;In my case, I have just returned from a &lt;a href=&quot;https://22.janczuk.org&quot;&gt;year-long, around-the-world travel&lt;/a&gt; with 25,000+ pictures, and I wanted to arrange some 500 of them in a contact-sheet-like 30”x20” photo collage poster to hang on a wall. I wanted each picture to have a custom description and the GPS location where the picture was taken, extracted from the EXIF metadata.&lt;/p&gt;

&lt;p&gt;When I started looking for a solution, no tool out there did exactly what I wanted, so I created my own: &lt;a href=&quot;https://github.com/tjanczuk/mkcollage&quot;&gt;tjanczuk/mkcollage&lt;/a&gt;. Here is a sample of what the tool can create, with a few pictures from south of the equator:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/post_images/2023-09-14/0.webp&quot; style=&quot;border-bottom:none;&quot;&gt;&lt;img src=&quot;/assets/post_images/2023-09-14/0.webp&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Contact sheet photo collage created with tjanczuk/mkcollage&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What can you do with &lt;a href=&quot;https://github.com/tjanczuk/mkcollage&quot;&gt;tjanczuk/mkcollage&lt;/a&gt;?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use image files from your local drive to create contact-sheet-like, poster-size photo collages.&lt;/li&gt;
  &lt;li&gt;Export the final collage image as PNG, JPEG, or WEBP.&lt;/li&gt;
  &lt;li&gt;Set the desired collage width and individual picture height.&lt;/li&gt;
  &lt;li&gt;Annotate images on the collage with custom descriptions or based on EXIF information in the image.&lt;/li&gt;
  &lt;li&gt;Select images to include or order images based on image metadata, including EXIF information (the example above used a filter that only included images with latitude below 8 degrees South).&lt;/li&gt;
  &lt;li&gt;Rich customization using CSS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the documentation for details, and happy collaging!&lt;/p&gt;
</description>
        <pubDate>Thu, 14 Sep 2023 00:00:00 +0000</pubDate>
        <link>https://tomasz.janczuk.org/2023/09/create-a-contact-sheet-photo-collage-poster.html</link>
        <guid isPermaLink="true">https://tomasz.janczuk.org/2023/09/create-a-contact-sheet-photo-collage-poster.html</guid>
        
        <category>photo collage</category>
        
        <category>contact sheet</category>
        
        <category>poster</category>
        
        <category>exif</category>
        
        <category>travel</category>
        
        
      </item>
    
      <item>
        <title>Scalable CRON job executor for AWS Lambda</title>
        <description>&lt;p&gt;Running scheduled jobs at scale in the cloud is an interesting problem to solve. In this post I will describe an architecture we came up with at &lt;a href=&quot;https://fusebit.io&quot;&gt;Fusebit&lt;/a&gt; that allows us to execute a large number of arbitrarily scheduled CRON jobs in AWS using Lambda, SQS, and CloudWatch.&lt;/p&gt;

&lt;h3 id=&quot;the-problem&quot;&gt;The problem&lt;/h3&gt;

&lt;p&gt;Say you are building a large-scale, multi-tenant system where tenants define programmatic jobs to be executed in the cloud on a given schedule. You have a large number of such jobs, each with a potentially different execution schedule.&lt;/p&gt;

&lt;p&gt;In the context of &lt;a href=&quot;https://fusebot.io&quot;&gt;Fusebit&lt;/a&gt;, we needed a solution to this problem to enable customers of our integration platform to implement and run integration logic on a given schedule. For example, to export new leads from HubSpot to MailChimp every night, or to send a status report based on Jira tickets to Slack every hour.&lt;/p&gt;

&lt;h3 id=&quot;the-non-scalable-cron-solution&quot;&gt;The non-scalable CRON solution&lt;/h3&gt;

&lt;p&gt;We have decided to use AWS Lambda to run the customer-provided logic. While Lambda is not an ideal compute layer for all types of integration workloads, in our case it satisfied the functional requirements while providing a convenient way of scaling and isolating workloads of multiple tenants.&lt;/p&gt;

&lt;p&gt;Once the infrastructure was in place to run the customer code, the next step was to trigger it on a customer-defined schedule. There are many ways to trigger execution of a Lambda function in AWS. One of them uses &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html&quot;&gt;scheduled CloudWatch Events&lt;/a&gt;. On the surface, CloudWatch Events meet all the requirements. You can define an arbitrary CRON schedule following which a CloudWatch Event will be triggered, and attach a specific Lambda function to execute in response.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/post_images/2021-05-27/0.svg&quot; style=&quot;border-bottom:none;&quot;&gt;&lt;img src=&quot;/assets/post_images/2021-05-27/0.svg&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Non-scalable CRON executor for AWS Lambda&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem with using CloudWatch Events in a highly scalable system is the limits. Per AWS region and account, one can only support up to 100 scheduled events (as of this writing), which was not sufficient to support the needs of a highly scalable multi-tenant system. We clearly needed a different approach.&lt;/p&gt;

&lt;h3 id=&quot;the-scalable-cron-solution&quot;&gt;The scalable CRON solution&lt;/h3&gt;

&lt;p&gt;After looking around for the right building blocks, a lesser known feature of Amazon SQS drew our attention: &lt;a href=&quot;https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-delay-queues.html&quot;&gt;SQS delay queues&lt;/a&gt;. The feature enables a message published to an SQS queue to remain invisible to consumers for a configurable time, up to 15 minutes.&lt;/p&gt;

&lt;p&gt;Equipped with this new tool, we have split our CRON processing pipeline by adding SQS delay queues in the middle, and using two additional Lambda functions: a &lt;em&gt;scheduler&lt;/em&gt; and an &lt;em&gt;executor&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;First, we defined a &lt;em&gt;single&lt;/em&gt; scheduled CloudWatch Event that triggered the scheduler Lambda function every 10 minutes, starting at minute 8 of an hour. So the scheduler Lambda was running at minute 8, 18, 28, 38 etc. of every hour.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/post_images/2021-05-27/1.svg&quot; style=&quot;border-bottom:none;&quot;&gt;&lt;img src=&quot;/assets/post_images/2021-05-27/1.svg&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Scalable CRON executor for AWS Lambda&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The purpose of the scheduler Lambda was to consider all scheduled jobs in the system (possibly millions), and select those that were due for execution in the subsequent whole-10-minute interval. For example, if the scheduler Lambda was running at 3:18, it would determine all scheduled job executions that need to occur in the 3:20-3:30 time span. The scheduler Lambda would then enqueue those job definitions to the SQS queue, setting the delayed delivery for each to correspond to the exact intended moment of execution of that job. For example, if a job was to run at 3:24, the scheduler Lambda running at 3:18 would enqueue that job to SQS setting the delayed execution to 6 minutes. The granularity of delayed execution allows the execution time to be set with 1 second precision.&lt;/p&gt;

&lt;p&gt;Lastly, the executor Lambda would consume messages from SQS as they are released following their delayed delivery settings. The executor Lambda would then invoke the appropriate customer-defined Lambda function to execute the intended logic.&lt;/p&gt;

&lt;p&gt;With this architecture, we were able to use a single CloudWatch Event to schedule a very large number of jobs and support our scheduled integration needs.&lt;/p&gt;

&lt;h3 id=&quot;shameless-plug&quot;&gt;Shameless plug&lt;/h3&gt;

&lt;p&gt;This is just one of many interesting technical problems we are continuously solving when building the integration platform at Fusebit. If you enjoy working on developer-centric products and cracking this type of technical problems on a daily basis and in a good company, &lt;a href=&quot;https://fusebit.io/careers&quot;&gt;we are hiring&lt;/a&gt;!&lt;/p&gt;
</description>
        <pubDate>Thu, 27 May 2021 00:00:00 +0000</pubDate>
        <link>https://tomasz.janczuk.org/2021/05/scalable-cron-executor-for-aws-lambda.html</link>
        <guid isPermaLink="true">https://tomasz.janczuk.org/2021/05/scalable-cron-executor-for-aws-lambda.html</guid>
        
        <category>serverless</category>
        
        <category>aws</category>
        
        <category>cron</category>
        
        <category>lambda</category>
        
        <category>sqs</category>
        
        <category>cloudwatch</category>
        
        <category>fusebit</category>
        
        
      </item>
    
      <item>
        <title>Embedded NPS widget with webtask.io</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Net_Promoter&quot;&gt;Net Promoter Score (NPS)&lt;/a&gt; has been an established tool for measuring customer loyalty since 2003. It is based on asking your customers a single question: how likely are they to recommend your product to others? Research shows that the answer is correlated to the future success of the product.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-08-31/0.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;How likely are you to recommend Extend to your friend or colleauge?&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In this post I will show how you can create a free NPS widget, complete with UI, backend storage, and reporting using &lt;a href=&quot;https://webtask.io&quot;&gt;Auth0 Webtasks&lt;/a&gt;, and embed this NPS widget with one line of HTML into your web application, single page app, or a blog post.&lt;/p&gt;

&lt;h3 id=&quot;taking-the-high-road&quot;&gt;Taking the high road&lt;/h3&gt;

&lt;p&gt;Start by installing and initializing &lt;em&gt;wt-cli&lt;/em&gt; if you have not already:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; wt-cli
wt init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, create your free NPS widget hosted as a serverless endpoint on &lt;a href=&quot;https://webtask.io&quot;&gt;webtask.io&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wt create &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; nps-blog &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; webtask-compiler &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--meta&lt;/span&gt; wt-compiler&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;webtask-compiler/nps &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You will get back a URL we will call &lt;em&gt;{webtask_url}&lt;/em&gt;. This is the endpoint of your NPS survey.&lt;/p&gt;

&lt;p&gt;Next, use an &lt;em&gt;iframe&lt;/em&gt; to embed your NPS survey in your web site, single page app, blog post, or wherever you host your HTML:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;How likely are you to recommend MyProduct to your friend or colleague?&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;iframe&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{webtask_url}&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;border: 0; height: 1em; width: 11em;&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, sit back and watch as NPS survey results pour in when your customers visit your site and share their opinion:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-08-31/1.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;How likely are you to recommend MyProduct to your friend or colleauge?&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Lastly, to view the results of your NPS survey, navigate to &lt;em&gt;{webtask_url}/stats&lt;/em&gt;. You will get back a JSON document with the histogram of all answers as well the number of NPS promoters, detractors, passives, as well as the overall NPS score:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;histogram&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;5&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;6&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;7&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;9&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;10&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;avg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;8.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;nps&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;promoters&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;passives&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;detractors&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;score&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Congratulations on your NPS score of 40%! MyProduct has a bright future.&lt;/p&gt;

&lt;h3 id=&quot;test-it-out&quot;&gt;Test it out&lt;/h3&gt;

&lt;p&gt;Now that you experienced creating your own, embeeded NPS widget, I need to ask you a crucial question:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;How likely are you to recommend webtask.io NPS widget to your friend or colleauge?&lt;/strong&gt;&lt;/p&gt;
  &lt;iframe src=&quot;https://wt-53f70144dc9d7c76455fa91f858d4cec-0.sandbox.auth0-extend.com/nps-blog?size=2em&quot; style=&quot;border: 0; height: 4em; width: 22em;&quot;&gt;&lt;/iframe&gt;

&lt;/blockquote&gt;

&lt;p&gt;If you answered 9 or 10, you have a moral obligation to &lt;a href=&quot;https://twitter.com/intent/tweet?text=Embed%20a%20free%20NPS%20survey%20on%20your%20site%20and%20find%20out%20what%20customers%20think%20about%20you!&amp;amp;url=https://tomasz.janczuk.org/2018/08/free-embedded-nps-widget-with-webtask-io.html&amp;amp;via=tjanczuk&amp;amp;hashtags=nps,webtaskio,serverless,product&quot;&gt;tweet this blog post&lt;/a&gt; with a nice comment.&lt;/p&gt;

&lt;p&gt;To check the results of the survey above, click &lt;a href=&quot;https://wt-53f70144dc9d7c76455fa91f858d4cec-0.sandbox.auth0-extend.com/nps-blog/stats&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;wait-what-how&quot;&gt;Wait. What, how?&lt;/h3&gt;

&lt;p&gt;Your NPS widget is an HTTP endpoint created with &lt;a href=&quot;https://webtask.io&quot;&gt;webtask.io&lt;/a&gt;. Webtask.io is a sample, freemium deployment of the &lt;a href=&quot;https://goextend.io/webtaskio&quot;&gt;Extend&lt;/a&gt; platform, a powerful solution for extending and customizing SaaS platforms.&lt;/p&gt;

&lt;p&gt;The NPS webtask responds to HTTP GET requests by serving HTML representing the survey, allowing users to click to enter their answer, and making an HTTP POST request back to itself to register that answer. Survey results are stored in &lt;a href=&quot;https://webtask.io/docs/storage&quot;&gt;webtask storage&lt;/a&gt;, and disambibuated between individual users using an HTTP cookie randomly generated and set if not already provided on the HTTP POST request. This is of course not a waterproof way of ensuring one user can only provide a single answer (since cookies can be purged or anonymous sessions used), but in many situations more than adequate. Cookies also allow the widget to present the user with the last answer they provided on subsequent visits to your site, and change that rating without double-counting.&lt;/p&gt;

&lt;p&gt;The last function of the widget is to respond to HTTP GET requests to &lt;em&gt;{webtask_url}/stats&lt;/em&gt; with a JSON document that represents the statistical results of the survey, including the NPS score.&lt;/p&gt;

&lt;p&gt;If you are interested in the technical details of the implementation, as well as many other cool things you can do with &lt;a href=&quot;https://webtask.io&quot;&gt;Auth0 Webtasks&lt;/a&gt;, check out &lt;a href=&quot;https://github.com/tjanczuk/wtc/blob/master/README.md#embeddable-nps-widget&quot;&gt;tjanczuk/wtc&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;shameless-plug&quot;&gt;Shameless plug&lt;/h3&gt;

&lt;p&gt;If you enojoy the flexibility of Auth0 Webtasks, you may be interested in the commercial product we’ve built on top of this technology: &lt;a href=&quot;https://goextend.io/webtaskio&quot;&gt;Extend&lt;/a&gt;. Extend removes friction from the customization and integration of SaaS platforms by providing an embedded scripting experience, &lt;a href=&quot;https://tomasz.janczuk.org/2018/03/serverless-webhooks-to-revolutionize-the-saas.html&quot;&gt;“serverless wehbooks”&lt;/a&gt;. Check it out!&lt;/p&gt;
</description>
        <pubDate>Fri, 31 Aug 2018 00:00:00 +0000</pubDate>
        <link>https://tomasz.janczuk.org/2018/08/free-embedded-nps-widget-with-webtask-io.html</link>
        <guid isPermaLink="true">https://tomasz.janczuk.org/2018/08/free-embedded-nps-widget-with-webtask-io.html</guid>
        
        <category>serverless</category>
        
        <category>webtask</category>
        
        <category>webtasks</category>
        
        <category>nps</category>
        
        <category>html</category>
        
        <category>widget</category>
        
        <category>nodejs</category>
        
        <category>javascript</category>
        
        
      </item>
    
      <item>
        <title>Build your own Twitter scheduler</title>
        <description>&lt;p&gt;Twitter is great for marketing your product or personal brand, but it has one disadvantage: you must actually tweet.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-04-05/0.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Twitter scheduler&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the next 2 minutes I will show you how to create a simple yet flexible (and free) Twitter scheduler that allows you to set up your tweets along with a tweeting schedule ahead of time. You can then focus your attention and energy elsewhere, while &lt;a href=&quot;webtask.io&quot;&gt;Auth0 Webtasks&lt;/a&gt; dilligently follow your tweeting directions.&lt;/p&gt;

&lt;h3 id=&quot;your-own-twitter-scheduler&quot;&gt;Your own Twitter scheduler&lt;/h3&gt;

&lt;p&gt;Start by installing and initializing &lt;em&gt;wt-cli&lt;/em&gt; if you have not already:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; wt-cli
wt init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, create your intial tweet schedule in YAML in the &lt;em&gt;buffer.yaml&lt;/em&gt; file. You can change the schedule later:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; buffer.yaml &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
tweets:
  - text: &quot;Check out this free Twitter scheduler that uses @auth0_extend and @webtaskio: https://tomasz.janczuk.org/2018/04/build-your-own-twitter-scheduler.html&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;#nodejs #serverless&quot;
    media: https://tomasz.janczuk.org/assets/images/b_1.jpg
    schedule: 
      - 4/4/2018 09:00 PDT
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(More on the format later).&lt;/p&gt;

&lt;p&gt;Last step: create a CRON job on &lt;a href=&quot;https://webtask.io&quot;&gt;Auth0 Webtasks&lt;/a&gt; that will run every 15 minutes, inspect your schedule, and send out any due tweets:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wt cron create buffer.yaml &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; buffer &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--schedule&lt;/span&gt; 15m &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--no-auth&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; webtask-compiler &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--meta&lt;/span&gt; wt-compiler&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;webtask-compiler/twitter &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;TWITTER_CONSUMER_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;YOUR_TWITTER_CONSUMER_KEY&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;TWITTER_CONSUMER_SECRET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;YOUR_TWITTER_CONSUMER_SECRET&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;TWITTER_ACCESS_TOKEN_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;YOUR_TWITTER_ACCESS_TOKEN_KEY&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;TWITTER_ACCESS_TOKEN_SECRET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;YOUR_TWITTER_ACCESS_TOKEN_SECRET&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(You must substitute your own Twitter credentials which you can get from &lt;a href=&quot;https://apps.twitter.com/&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That’s it. Sit down and watch your tweets beeing sent out.&lt;/p&gt;

&lt;h3 id=&quot;wait-what-how&quot;&gt;Wait. What, how?&lt;/h3&gt;

&lt;p&gt;Your Twitter scheduler is an &lt;a href=&quot;https://webtask.io/docs/cron&quot;&gt;Auth0 Webtask CRON job&lt;/a&gt;. It runs every 15 minutes (or whetever frequency you choose). It inspects the tweeting schedule you set via the YAML, and sends out any tweets that became overdue since the last time the CRON job ran. The CRON job stores the time it executed last as well as the status of the most recent tweets that were sent in &lt;a href=&quot;https://webtask.io/docs/webtask_storage&quot;&gt;webtask storage&lt;/a&gt;. All this logic is implemented in a &lt;a href=&quot;https://webtask.io/docs/webtask-compilers&quot;&gt;webtask compiler&lt;/a&gt; which is defined in the &lt;a href=&quot;https://github.com/tjanczuk/wtc#twitter-scheduler&quot;&gt;webtask-compiler NPM module&lt;/a&gt;. In addition to implementing the actual tweeting logic, the compiler allows the use of a custom DSL (the YAML with tweeting schedule in this case) to be used as the &lt;em&gt;script&lt;/em&gt; of the webtask.&lt;/p&gt;

&lt;p&gt;For a gentle introduction of the powerful &lt;em&gt;webtask compiler&lt;/em&gt; concept, check out &lt;a href=&quot;https://auth0.com/blog/expanding-auth0-extend-with-compilers/&quot;&gt;Raymond Camden’s post&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;updating-the-tweet-schedule&quot;&gt;Updating the Tweet schedule&lt;/h3&gt;

&lt;p&gt;Given that the YAML is the &lt;em&gt;source code&lt;/em&gt; of your webtask, you can simply use the Webtask Editor to modify it. Run&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wt edit buffer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;which will bring up the webtask editor allowing you to change the YAML.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-04-05/1.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Webtask Editor&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Make any changes, save, and you are done!&lt;/p&gt;

&lt;p&gt;This is what you can do in the Yaml:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;na&quot;&gt;tweets&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;just&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;installed&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;free&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Twitter&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;scheduler&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;that&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@auth0_extend&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@webtaskio.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Check&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;https://github.com/tjanczuk/wtc#twitter-scheduler&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;#nodejs&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;#serverless&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://tomasz.janczuk.org/assets/images/b_1.jpg&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://tomasz.janczuk.org/assets/images/b_2.jpg&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;4/8/2018 09:00 PDT&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;4/8/2018 15:00 PDT&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;You&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;can&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;do&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;amazing&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;things&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;webtask.io!&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;#nodejs&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;#serverless&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://tomasz.janczuk.org/assets/images/b_2.jpg&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;4/5/2018 12:00 PDT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;The top level object must contain the &lt;em&gt;tweets&lt;/em&gt; array with elements representing individual tweets.&lt;/li&gt;
  &lt;li&gt;Each tweet must contain the &lt;em&gt;text&lt;/em&gt; of the tweet, and a &lt;em&gt;schedule&lt;/em&gt; at minimum. It may also contain the &lt;em&gt;media&lt;/em&gt; to attach to the tweet.&lt;/li&gt;
  &lt;li&gt;The &lt;em&gt;schedule&lt;/em&gt; is a single date or an array of dates that tweet will be sent on. You can use any format here that is accepted by Node’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new Date(...)&lt;/code&gt; constructor, like the simple one shown above. It is a good idea to specify the time zone.&lt;/li&gt;
  &lt;li&gt;The &lt;em&gt;media&lt;/em&gt; element, if present, is a single URL or an array of up to 4 URLs. These must be publicly accessible URLs that serve the media (typically an image) to attach to your tweet, with correct &lt;em&gt;Content-Type&lt;/em&gt; header.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;get-the-status&quot;&gt;Get the status&lt;/h3&gt;

&lt;p&gt;You can easily check the status of your Twitter scheduler by navigating to your webtask’s URL in the browser:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-04-05/2.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Status&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;schedule&lt;/em&gt; element shows the JSON representation of your YAML tweeting schedule, or an error if the YAML could not be parsed. The &lt;em&gt;plan&lt;/em&gt; element specifies the Tweets that would be sent if the CRON job were to run &lt;em&gt;now&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The last part of the status specifies the 20 most recently sent tweets:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-04-05/3.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Tweet History&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For each tweet, the &lt;em&gt;result&lt;/em&gt; element tells if the tweet was sent successfuly or not, and the unique tweet id in case you want to automate further processing.&lt;/p&gt;

&lt;h3 id=&quot;force-the-execution-now&quot;&gt;Force the execution, now&lt;/h3&gt;

&lt;p&gt;Instead of waiting for the CRON job to execute, you can force execution of your schedule. This is done by simply navigating to the webtask URL with a &lt;em&gt;?run&lt;/em&gt; query string appended, e.g.:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://{your_container}.sandbox.auth0-extend.com/{your_webtask}?run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As a result, all the overdue tweets since the last execution (either by the CRON job or manually) will be sent.&lt;/p&gt;

&lt;h3 id=&quot;shameless-plug&quot;&gt;Shameless plug&lt;/h3&gt;

&lt;p&gt;If you enojoy the flexibility of Auth0 Webtasks, you may be interested in the commercial product we’ve built on top of this technology: &lt;a href=&quot;https://goextend.io?utm_source=blog&amp;amp;utm_medium=post&amp;amp;utm_campaign=blog-tomek&amp;amp;utm_content=2018-04-05-twitter-scheduler&quot;&gt;Extend&lt;/a&gt;. Extend removes friction from the customization and integration of SaaS platforms by providing an embedded scripting experience, &lt;a href=&quot;https://tomasz.janczuk.org/2018/03/serverless-webhooks-to-revolutionize-the-saas.html&quot;&gt;“serverless wehbooks”&lt;/a&gt;. Check it out!&lt;/p&gt;
</description>
        <pubDate>Thu, 05 Apr 2018 00:00:00 +0000</pubDate>
        <link>https://tomasz.janczuk.org/2018/04/build-your-own-twitter-scheduler.html</link>
        <guid isPermaLink="true">https://tomasz.janczuk.org/2018/04/build-your-own-twitter-scheduler.html</guid>
        
        <category>serverless</category>
        
        <category>webtask</category>
        
        <category>webtasks</category>
        
        <category>auth0</category>
        
        <category>nodejs</category>
        
        <category>twitter</category>
        
        <category>scheduler</category>
        
        <category>javascript</category>
        
        
      </item>
    
      <item>
        <title>How to build your own serverless platform</title>
        <description>&lt;p&gt;In this post I will provide a blueprint for building your own serverless platform, based on the lessons learned from working on &lt;a href=&quot;https://goextend.io&quot;&gt;Auth0 Extend&lt;/a&gt; in the recent years.&lt;/p&gt;

&lt;h3 id=&quot;why-would-you-want-to-build-a-serverless-platform&quot;&gt;Why would you want to build a serverless platform?&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;serverless&lt;/em&gt; (Function-as-a-Service) computation model is a powerful complement to the webhook-based extensibility of a SaaS platform.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In the previous post I proposed that &lt;a href=&quot;http://localhost:4000/2018/03/serverless-webhooks-to-revolutionize-the-saas.html&quot;&gt;Serverless webhooks will revolutionize the SaaS&lt;/a&gt; by removing friction from customizing SaaS platforms and integrating external systems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Serverless webhooks benefit end users, sales teams, and ultimately make the SaaS platform more successful.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-07/1.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;Serverless webhooks&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A growing number of SaaS platforms embrace the serverless webhook model. &lt;a href=&quot;https://www.twilio.com/functions&quot;&gt;Twilio Functions&lt;/a&gt; and &lt;a href=&quot;https://www.netlify.com/tags/functions/&quot;&gt;Netlify Functions&lt;/a&gt; are two recent examples.&lt;/p&gt;

&lt;p&gt;If you want to enable the serverless webhook model in your SaaS, you may be considering building your own serverless platform to support it. The rest of this post provides one design informed by the experience of building &lt;a href=&quot;https://auth0.com/extend&quot;&gt;Auth0 Extend&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;requirements&quot;&gt;Requirements&lt;/h3&gt;

&lt;p&gt;In order to complement a webhook-based extensibility model of your SaaS product, your serverless platform will need to meet this minimum set of requirements:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It must offer a core concept of a &lt;em&gt;function&lt;/em&gt; as a unit of custom business logic that runs when a webhook in your SaaS platform is triggered.&lt;/li&gt;
  &lt;li&gt;It must support adequately rich programming environment. In this post, I will assume that Node.js with the ecosystem of all public NPM modules (over 500k) is sufficient.&lt;/li&gt;
  &lt;li&gt;It must offer an API surface that allows managing functions as individual webhooks.&lt;/li&gt;
  &lt;li&gt;When a webhook event is triggered in your SaaS platform, you must be able to execute the corresponding function to handle the webhook HTTP request.&lt;/li&gt;
  &lt;li&gt;The serverless platform must provide a mechanism to isolate the execution of functions of eache tenant, since each tenant can author their own set of functions with untrusted and potentially malicious code.&lt;/li&gt;
  &lt;li&gt;The latency of execution of your serverless webhooks must be acceptably small and predictable. In particular, the cold and warm latency cannot vary too much - this is particularly important if they execute as part of a UI interaction with the end user of your SaaS.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In practice, your specific scenario may have many more requirements. The design blueprint in this post only addresses the ones above.&lt;/p&gt;

&lt;p&gt;Let’s start designing a serverless platform from the bottom up. We will use basic building blocks so that the design is relatively easy to port to a variety of environments.&lt;/p&gt;

&lt;h3 id=&quot;compute&quot;&gt;Compute&lt;/h3&gt;

&lt;p&gt;Execution of functions requires basic computing resources: CPU, memory, disk, and network. We will then start with an empty VM.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-22/0.svg&quot; class=&quot;tj-img-diagram-50&quot; alt=&quot;VM&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We will tackle scalability later. Right now let’s focus on the components running within the VM.&lt;/p&gt;

&lt;h3 id=&quot;proxy&quot;&gt;Proxy&lt;/h3&gt;

&lt;p&gt;Serverless webhooks as well as APIs that allow their management are both exposed over HTTP. HTTP endpoints will be the main entry into the functionality of our VM, and an HTTP proxy is a component that will decide how to process a particular request.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-22/1.svg&quot; class=&quot;tj-img-diagram-50&quot; alt=&quot;VM with Proxy&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can use a variety of technologies for the proxy: HAProxy, Nginx, all the way to writing your own.&lt;/p&gt;

&lt;h3 id=&quot;management-apis&quot;&gt;Management APIs&lt;/h3&gt;

&lt;p&gt;At a minimum, the management APIs of our serverless platform need to allow for basic CRUD operations on serverless webhooks. You need to be able to create, update, read, delete, and list available webhooks. So you need a specialised web server component implementing these APIs. The proxy must be able to route the management API requests to this component.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-22/2.svg&quot; class=&quot;tj-img-diagram-50&quot; alt=&quot;VM with Proxy, Management APIs&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Management APIs themselves are stateless. Any durable representation of a serverless webhook, most notably its code, must be stored outside of the context of this component.&lt;/p&gt;

&lt;h3 id=&quot;persistent-storage&quot;&gt;Persistent storage&lt;/h3&gt;

&lt;p&gt;Persistent storage is required to maintain any durable state representing a serverless function. At a minimum, this is the code of the function itself with a manifest of module dependencies. Depending on your scenario, this state may also include additional configuration that the code requires at runtime, like API keys, connection strings, or other metadata that controls the execution of the function.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-22/3.svg&quot; class=&quot;tj-img-diagram-75&quot; alt=&quot;VM with Proxy, Management APIs, Storage&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The essence of the &lt;em&gt;Function-as-a-Service&lt;/em&gt; paradigm is that individual functions are small units of business logic, unlike large monolithic web apps of the days past. Many standard databases or storage solutions should be a good fit to store this data. MongoDB, DynamoDB, MySQL should all fit the bill.&lt;/p&gt;

&lt;p&gt;Now that you can manage your serverless functions via HTTP APIs, let’s move on to the interesting part - executing them with low latency and adequate isolation.&lt;/p&gt;

&lt;h3 id=&quot;execution-model-the-theory&quot;&gt;Execution model: the theory&lt;/h3&gt;

&lt;p&gt;While you could say that every function is running completely isolated from other functions, it is beneficial to introduce a level of indirection.&lt;/p&gt;

&lt;p&gt;Consider a concept of a &lt;em&gt;container&lt;/em&gt; which denotes an isolation boundary for executing code. Any two functions running in distinct containers are &lt;em&gt;guaranteed&lt;/em&gt; to be isolated, while any two functions running within the same container &lt;em&gt;may&lt;/em&gt;, in fact, share the same process, address space, and therefore in-memory state. Such design has enough flexibility to support the isolation needs of a multi-tenant SaaS platform, at the same time enabling optimizations across serverless webhooks of a single tenant.&lt;/p&gt;

&lt;p&gt;Let’s then assume a model in which several serverless functions can be grouped together to run within the same isolation boundary of a &lt;em&gt;container&lt;/em&gt;. They can share the state with one another (e.g. DB connection pool) while maintaining logical isolation of the business logic. Such taxonomy of containers and functions must be supported by the management APIs described above, which is straightforward to solve. The interesting challenges lie in function execution mechanics.&lt;/p&gt;

&lt;p&gt;So, let’s design it.&lt;/p&gt;

&lt;h3 id=&quot;execution-model-the-practice&quot;&gt;Execution model: the practice&lt;/h3&gt;

&lt;p&gt;An HTTP request arrives as a result of a webhook being triggered in your SaaS. In response, the serverless platform must run a Node.js function, and do so with low latency and adequate isolation from other functions.&lt;/p&gt;

&lt;p&gt;Given such a request, a naive, CGI-like implementation would create an isolated execution environment, e.g. one using Linux containers and namespaces, or Windows job objects, with some additional measures of security applied on top. It would load the code of the function from the persistent storage, fetch the module dependency manifest, and provision those modules. It would then execute the function in this environment, return results to the caller, and clean up after itself.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;This is a great story, especially that it affords the end user ample time to go to lunch and file their tax return.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In most scenarios, the latency introduced by the overhead of creating isolated execution environments for every request is not acceptable, even in the lightweight world of Linux containers.&lt;/p&gt;

&lt;p&gt;One solution to this problem is to reuse &lt;em&gt;containers&lt;/em&gt; to process multiple requests of the same tenant, similar to how FastCGI improved over CGI. Container reuse will reduce warm latency. To enable processing of multiple requests by the same container, the protocol over which these requests are passed to the container must be defined. It is convenient for that protocol to be HTTP, as that will allow the VM-level proxy to simply reverse proxy serverless webhook execution requests to the container associated with the tenant who wrote the serverless webhook.&lt;/p&gt;

&lt;p&gt;In order to pull this design off, we need a component that will manage the lifetime of the containers and their association with a specific tenant. Let’s call this component a &lt;em&gt;container handler&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-22/4.svg&quot; class=&quot;tj-img-diagram-75&quot; alt=&quot;VM with Proxy, Management APIs, Storage, Container Handler&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When a request to execute a specific serverless webhook arrives at the proxy, the proxy consults the container handler to obtain the address of the specific container that should handle this request. If a container assigned to a specific tenant is already running, the container handler promptly returns its address to the proxy which will in turn reverse proxy the request to that container.&lt;/p&gt;

&lt;p&gt;A problem will arise when the proxy receives a request that needs to execute in a container that is not yet running. In this case, the container handler creates a new container and provision the requested serverless function within it based on the data in the external storage. This can incur a substantial performance penalty.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Cold startup latency is one of the challenges of serverless platforms. How do you create lightweight, isolated execution environments and provision them with necessary components quickly?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We don’t want our cold startup latency to be substantially different from warm latency. Let’s go back to the drawing board.&lt;/p&gt;

&lt;h3 id=&quot;reducing-cold-startup-latency-part-1&quot;&gt;Reducing cold startup latency, part 1&lt;/h3&gt;

&lt;p&gt;A lot of the work related to starting up a new container is generic and can, therefore, be front-loaded. Let’s have the container handler maintain a pool of pre-warmed containers ready to be assigned to tenants as necessary.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-22/5.svg&quot; class=&quot;tj-img-diagram-75&quot; alt=&quot;VM with Proxy, Management APIs, Storage, Container Handler, Container Pool&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With this design, when a request arrives on behalf of a tenant that does not yet have a running container, the container handler picks up one unassigned container from the pool, assigns it to the tenant for life, and notifies the proxy where to route the request. We have now cut out a substantial part of the container initialization time from the cold startup path: the creation of a container, and starting up of a process that runs a mini-HTTP server within it.&lt;/p&gt;

&lt;p&gt;What work still remains to be done in the cold startup path? The container handler must obtain the code of the serverless wehbook from Storage and provision it within the container, along with the module dependencies. Neither the function code or the module dependencies are known at the time the generic container is created, so they need to be provisioned lazily when a message arrives. Anyone implementing Node.js applications knows that the size of the module dependency closure (the overall size of the &lt;em&gt;node_modules&lt;/em&gt; directory) can grow rather quickly and adversely impact provisioning time.&lt;/p&gt;

&lt;p&gt;We are not done with the cold startup latency problem yet. How do we solve the module provisioning problem?&lt;/p&gt;

&lt;h3 id=&quot;reducing-cold-startup-latency-part-2&quot;&gt;Reducing cold startup latency, part 2&lt;/h3&gt;

&lt;p&gt;If reducing startup latency is an objective, running &lt;em&gt;npm install&lt;/em&gt; to provision Node.js modules based on the dependency manifest of a serverless webhook while on the hot path of message processing is just an obvious &lt;em&gt;no-go&lt;/em&gt;. The process can take arbitrarily long depending on how many modules need to be pulled from NPM and whether any build steps are necessary.&lt;/p&gt;

&lt;p&gt;Doing so during serverless webhook provisioning via the management APIs will be a much more acceptable solution, as development usually has less strict latency expectations compared to the runtime. Given that, we are going to front-load building of the module dependency closure of a serverless function by executing it during the management API call that creates the function. This is great, but there is more.&lt;/p&gt;

&lt;p&gt;Another interesting aspect of this problem is related to duplicated effort. If several functions have a dependency on the same module, will we really need to build this module anew each time? After all &lt;em&gt;async@2.6.0&lt;/em&gt; is &lt;em&gt;async@2.6.0&lt;/em&gt; is &lt;em&gt;async@2.6.0&lt;/em&gt;, regardless how many serverless functions depend on it. So let’s try to come up with a design that further optimizes around reuse of pre-built Node.js modules across different serverless webhooks, even when they are created by different tenants.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-22/6.svg&quot; class=&quot;tj-img-diagram-75&quot; alt=&quot;VM with Proxy, Management APIs, Storage, Container Handler, Container Pool, Module Builder&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When a management API is called to create or update a serverless webhook, its module dependencies are inspected. For each dependency (e.g. &lt;em&gt;async@2.6.0&lt;/em&gt;) that is not yet available in Storage in a built form, the management API schedules it to be built with the &lt;em&gt;module builder&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The module builder is a background worker process running on a separate VM that will receive its work through a queue mechanism using technologies like AWS SQS, RabbitMQ, or similar. Its job is to receive a module build request, run &lt;em&gt;npm install&lt;/em&gt; to build this module in the same environment in which it will eventually be used at runtime, capture the entire, built dependency closure (the entire &lt;em&gt;node_modules&lt;/em&gt; folder), tar, zip, and put in Storage.&lt;/p&gt;

&lt;p&gt;Since every module is considered for building individually, every version of every module will only be built once in the system as a whole. Given that, over time the latency of creating serverless webhooks via the management APIs becomes negligible, since all common module dependencies have already been built before.&lt;/p&gt;

&lt;p&gt;At runtime, when the container handler prepares an unassigned container for execution of a specific serverless webhook, it first obtains the code of the function from Storage and then proceeds to provision modules the function depends on. This is done by obtaining the pre-built modules from Storage, unpacking them on the local volume on the VM, manufacturing a synthetic &lt;em&gt;node_modules&lt;/em&gt; directory with symlinks to the read-only filesystem holding the physical module on the host, and attaching a volume with this directory to a specific container. This makes all module dependencies available to the serverless webhook code running within the container.&lt;/p&gt;

&lt;p&gt;The added benefits of storing pre-compiled modules on the host VM’s filesystem and sharing them read-only with all containers and functions that require them include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;reduction of storage requirements,&lt;/li&gt;
  &lt;li&gt;reduction of network bandwidth to download the modules,&lt;/li&gt;
  &lt;li&gt;and further reduction of cold startup latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For serverless webhooks for which module dependencies had already been downloaded by previously running functions, even by other tenants, the container handler does not even need to reach out to Storage.&lt;/p&gt;

&lt;p&gt;With the design described above, warm and cold startup latency of serverless webhooks is very similar, which is an important consideration in a range of SaaS extensibility scenarios.&lt;/p&gt;

&lt;h3 id=&quot;scalability-and-reliability&quot;&gt;Scalability and reliability&lt;/h3&gt;

&lt;p&gt;What we have designed so far is a single VM solution that provides a self-contained, multi-tenant platform for managing and executing serverless webhooks. So what happens when they come, and the traffic explodes?&lt;/p&gt;

&lt;p&gt;Given that one VM is self-contained and can function independently, you can accommodate increasing throughput by scaling out the number of VMs and putting a proxy in front of the cluster. If you use a flexible scaling technology like AWS’s Auto Scaling Groups, the size of the cluster can be dynamically adjusted as the load changes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-22/7.svg&quot; class=&quot;tj-img-diagram-75&quot; alt=&quot;VM with Proxy, Management APIs, Storage, Container Handler, Container Pool, Module Builder, ASG, ELB&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the current design, the VMs are independent of each other. This is hugely helpful in increasing the overall reliability of the deployment. Lack of state shared between VMs means they can crash at any time (and they will, the frequency of which depends on your choice of specific technologies and the underlying platform) without affecting the remaining VMs. The Auto Scaling Group, or a similar solution, can subsequently replace a failed VM with a new one.&lt;/p&gt;

&lt;p&gt;This reliability advantage has a drawback, however. A container for a given tenant may exist &lt;em&gt;on every VM&lt;/em&gt; at the same time. If you want to support an increasing number of tenants running code concurrently, this can only be accommodated by scaling up - increasing the capacity of individual VMs in the cluster. This works up to a point - beyond a certain threshold of density you will need to consider adding smarts to the container handler component to be able to handle placement of a tenant’s container on just a subset of VMs in the cluster. This would require a technology to efficiently share a synchronized routing table data across the cluster, e.g. Zookeeper, etcd, or a similar solution. Such design is more complex and beyond the scope of this post.&lt;/p&gt;

&lt;h3 id=&quot;are-we-done-yet&quot;&gt;Are we done yet?&lt;/h3&gt;

&lt;p&gt;Yes and no. After you have implemented the design above, you have a functioning, multi-tenant serverless platform that can power the serverless webhooks of your SaaS. Your CPO is happy. Your COO’s nightmare begins.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The system is up and running. The easy work is done. Now you need to run this system at 99.9…% availability, with 24/7 uptime, and zero-downtime upgrades.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The operations and devops of a serverless solution like the one described in this post is a project in itself, in many respects just as complex as the solution. Stay tuned for another post based on our experiences building and operating &lt;a href=&quot;https://goextend.io&quot;&gt;Auth0 Extend&lt;/a&gt; to those high operational standards.&lt;/p&gt;
</description>
        <pubDate>Sun, 25 Mar 2018 00:00:00 +0000</pubDate>
        <link>https://tomasz.janczuk.org/2018/03/how-to-build-your-own-serverless-platform.html</link>
        <guid isPermaLink="true">https://tomasz.janczuk.org/2018/03/how-to-build-your-own-serverless-platform.html</guid>
        
        <category>serverless</category>
        
        <category>webtask</category>
        
        <category>webtasks</category>
        
        <category>auth0</category>
        
        <category>nodejs</category>
        
        <category>SaaS</category>
        
        <category>webhook</category>
        
        <category>javascript</category>
        
        
      </item>
    
      <item>
        <title>Serverless webhooks to revolutionize the SaaS</title>
        <description>&lt;p&gt;In 2007, Jeff Lindsay introduced &lt;em&gt;webhooks&lt;/em&gt; in his &lt;a href=&quot;http://progrium.com/blog/2007/05/03/web-hooks-to-revolutionize-the-web/&quot;&gt;Webhooks to revolutionize the web&lt;/a&gt; article. Since then, webhooks have become the dominant mechanism for exposing events from web services. Fast forward to 2017, and a new, better pattern started to emerge: serverless webhooks.&lt;/p&gt;

&lt;h3 id=&quot;webhooks-2007&quot;&gt;Webhooks (2007)&lt;/h3&gt;

&lt;p&gt;This is how Jeff defined webhooks in his iconic article from 2007:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Webhooks are essentially user defined callbacks made with HTTP POST. To support webhooks, you allow the user to specify a URL where your application will post to and on what events. Now your application is pushing data out wherever your users want.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a very elegant and powerful concept. It allows for a clean, protocol-based decoupling of the system that exposes an event from the system that processes that event. It is flexible enough to allow for a wide range of scenarios, including system integration, data transformation, or customization of behavior. It also embraces the asynchronous nature of interactions between web services.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-07/0.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;2007 webhooks&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Using webhooks requires setting up and running another web service. Servers, devops, monitoring, SSL, uptime, failover strategy, etc. For years this has been a small price to pay given the benefits webhooks offered, and the lack of a better alternative. However, given cloud computing advances over the last decade, there is now an opportunity to further improve on this core concept.&lt;/p&gt;

&lt;h3 id=&quot;serverless-webhooks-2017&quot;&gt;Serverless webhooks (2017)&lt;/h3&gt;

&lt;p&gt;Fast forward from 2007 to 2017, through IaaS, PaaS, all the way to FaaS (Function-as-a-Service, aka &lt;em&gt;serverless&lt;/em&gt;). The essence of the serverless computing paradigm is to organize your logic into small units of computation you can refer to as &lt;em&gt;functions&lt;/em&gt;. A single function maps very well to a single webhook.&lt;/p&gt;

&lt;p&gt;Reacting to web service events with arbitrary business logic is the core value of webhooks. Imagine an experience where users of a SaaS platform can focus on implementing this business logic as a &lt;em&gt;function&lt;/em&gt; without having to run a service to expose it as an HTTP endpoint:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-07/1.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;2017 Serverless webhooks&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are several immediate advantages such a serverless webhook model brings for a SaaS platform compared to the original webhooks:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;The users&lt;/strong&gt; of a SaaS platform benefit from a dramatically improved experience of implementing custom event logic and the reduction of time to market: users can focus on the business logic without dealing with issues related to running a web service.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The sales engineers&lt;/strong&gt; of a SaaS company are enabled to rapidly prototype custom integrations with little effort, shortening the sales cycles and highlighting the power of the platform.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The support team&lt;/strong&gt; of a SaaS organization is equipped to better serve the customers since they have full insight into the implementation logic of the function handling the event.&lt;/li&gt;
  &lt;li&gt;Last but not least, &lt;strong&gt;the SaaS platform itself&lt;/strong&gt; is using cutting-edge extensibility technology based on the latest cloud computing trends, which sets it apart from the competition.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ten years after the introduction of the webhook concept, &lt;a href=&quot;https://twitter.com/progrium/status/864588610858881029&quot;&gt;this is what Jeff Lindsay says about the serverless webhook pattern&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/post_images/2018-03-07/2.png&quot; class=&quot;tj-img-diagram-100&quot; alt=&quot;This was the whole point of webhooks&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are many existing implementations of this serverless webhook paradigm in practice. The &lt;a href=&quot;https://www.twilio.com/functions&quot;&gt;Twilio Functions&lt;/a&gt; and &lt;a href=&quot;https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_dev_guide.htm&quot;&gt;Salesforce Apex&lt;/a&gt; are good examples. Until recently, these advanced solutions have been reserved for the SaaS platforms run by sophisticated, technology-savvy organizations.&lt;/p&gt;

&lt;h3 id=&quot;serverless-webhooks-for-your-saas&quot;&gt;Serverless webhooks for your SaaS&lt;/h3&gt;

&lt;p&gt;If you are operating a SaaS platform that could benefit from serverless webhooks, you may ask yourself what it takes to implement them. In the &lt;a href=&quot;https://tomasz.janczuk.org/2018/03/how-to-build-your-own-serverless-platform.html&quot;&gt;How to build your own serverless platform&lt;/a&gt; post, I  describe an architectural blueprint for building a serverless webhook solution for your own SaaS platform, based on the lessons learned from the &lt;a href=&quot;https://goextend.io&quot;&gt;Auth0 Extend&lt;/a&gt; product in Auth0.&lt;/p&gt;
</description>
        <pubDate>Thu, 08 Mar 2018 00:00:00 +0000</pubDate>
        <link>https://tomasz.janczuk.org/2018/03/serverless-webhooks-to-revolutionize-the-saas.html</link>
        <guid isPermaLink="true">https://tomasz.janczuk.org/2018/03/serverless-webhooks-to-revolutionize-the-saas.html</guid>
        
        <category>serverless</category>
        
        <category>webtask</category>
        
        <category>webtasks</category>
        
        <category>auth0</category>
        
        <category>nodejs</category>
        
        <category>SaaS</category>
        
        <category>webhook</category>
        
        <category>javascript</category>
        
        
      </item>
    
  </channel>
</rss>
