<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Miron Machnicki on Medium]]></title>
        <description><![CDATA[Stories by Miron Machnicki on Medium]]></description>
        <link>https://medium.com/@machnicki?source=rss-2bdfb43ca071------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*7aYrcppGUIZMNgY3AqCoNw.jpeg</url>
            <title>Stories by Miron Machnicki on Medium</title>
            <link>https://medium.com/@machnicki?source=rss-2bdfb43ca071------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 22:06:18 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@machnicki/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[My journey with AWS Serverless]]></title>
            <link>https://machnicki.medium.com/my-journey-with-aws-serverless-7db5d91ecfc4?source=rss-2bdfb43ca071------2</link>
            <guid isPermaLink="false">https://medium.com/p/7db5d91ecfc4</guid>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[aws-lambda]]></category>
            <dc:creator><![CDATA[Miron Machnicki]]></dc:creator>
            <pubDate>Wed, 30 Dec 2020 09:06:20 GMT</pubDate>
            <atom:updated>2020-12-30T20:28:28.732Z</atom:updated>
            <content:encoded><![CDATA[<h4>Tips and lesson learned about AWS Lambda and Dynamo DB in last year</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tu5vsjCwd87A6OTglcbsBQ.png" /><figcaption>background photo source: <a href="https://unsplash.com/@withluke">https://unsplash.com/@withluke</a></figcaption></figure><p>Serverless is a revolution for interpreted, single threaded languages such as Node.js or Python. JavaScript engineers have got possibility to write scalable and fast architecture without going too deep into multi threaded issues. Initialisation of Node.js and Python processes is fast, which makes them ideal choice to use for AWS Lambda. Functions can be run in many instances in parallel with autoscaling (based on real traffic). You can control that and eg. prevent spinning up too many instances (reserved concurrency) or set min. instances available to prevent cold starts (provisioned concurrency). One part of your application can be run eg. in 200 concurrent processes, when other in 10. And you pay only for what you’re using.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BIYjMx3KM76iEyZOZzpAYA.png" /><figcaption>You can scale your Lambda in many different ways, eg: a) start all possible instances, b) scale up 60 additional instances per minute to a maximum of 1,000 concurrent invocations (with SQS), c) set provisioned concurrency to always have min. number of instances running (to prevent cold starts), d) with reserved concurrency (to do not run more than max number of instances).</figcaption></figure><p>Here are some notes and tips, which might be helpful, if you are starting your journey with AWS Lambda and Dynamo DB.</p><h3>Async architecture with AWS Lambda</h3><h4>Split your logic</h4><p>The tricky part is to proper organise architecture — split logic into multiple functions and transfer data between them. That splitting makes sense especially if one part of logic is invoked more often than other, or if one part is more complicated, takes more time / memory to compute. Another reason could be, that for one function you would like to have concurrency limit— for example to avoid too many requests to external API or DB (throttling issues).</p><p>Keep in mind, that Lambda initialisation can take some time (not so much as compiled languages, though), so it might not be necessary to split logic into Lambdas, which computation time takes less &lt; 1s.</p><h4>Know the difference between sync and async Lambda</h4><p>You can invoke Lambda synchronously or asynchronously. If your logic is simple and you expect direct response from your function, invoke Lambda synchronously (InvocationType = RequestResponse). For example <a href="https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html">Lambda calls via API Gateway</a> or <a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html">Elastic Load Balancer</a> are synchronous, as you expect response.</p><p>For more complicated logic it’s recommend to split it between functions and invoke them asynchronously (InvocationType = Event). If you would like to call Lambda from other Lambda synchronously — keep in mind, that you may pay twice, as time of both Lambdas will be billed separately.</p><p>Example of <strong>async / event driven architecture</strong>: one Lambda sends message to topic (SNS), or Event Bus (EventBridge), or for example updates record in database, and another Lambda is subscribed to that specific event / stream. Communication in that case is event driven - there is no other Lambda waiting for response as it could be in sync model.</p><p>You can orchestrate logic using <a href="https://aws.amazon.com/step-functions">AWS Step Functions</a>, so your event driven application will be easier to maintain. If you would like to orchestrate async workflow, but expect sync response after workflow will be finished (eg. by API Gateway) — consider <a href="https://aws.amazon.com/blogs/compute/new-synchronous-express-workflows-for-aws-step-functions/">Synchronous Express Workflow</a>. For some applications AWS Step Functions might be too costly because of <a href="https://dzone.com/articles/cutting-step-functions-costs-on-enterprise-scale-w#:~:text=Why%20Step%20Functions%20Is%20Expensive,side%20of%20the%20Serverless%20trilemma.&amp;text=AWS%20recommends%20using%20Express%20workflows%20if%20tasks%20have%20short%20execution%20times.">double-billing issue</a>, so use it carefully. Sometimes it might be better to stick with <a href="https://aws.amazon.com/eventbridge/">EventBridge</a> or <a href="https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html#invocation-async-destinations">Lambda destinations</a> instead.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/994/1*DiU7Jh8z04UZfHoG3bas5w.png" /><figcaption>Within AWS Step Functions you can orchestrate event driven application // source: AWS</figcaption></figure><p><a href="https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html">Async Lambda</a> has its own <strong>event queue </strong>(don’t confuse with SQS), so you can implement retry logic in case event won’t be delivered (max 2 retries) and it can keep events for max. 6 hours, eg. when function doesn’t have enough capacity to handle all incoming requests (throttling errors). Keep in mind that event queue is eventually consistent (event can be sometimes delivered more than once). If you expect throttling issues, consider implementing <strong>SQS queue</strong> (instead of relying on event queue) to have guaranteed, that all events will be delivered.</p><p><a href="https://docs.aws.amazon.com/lambda/latest/dg/invocation-retries.html">Do not forget about handling errors</a> — you can set SQS queue or SNS topic as <strong>dead letter queue</strong> or as an <strong>on failure destination</strong>. In first case you will have access to all discarded events, in second case you will have access to events, but also to errors responses.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cBnO7j9EIB1lg-0WSaC0BA.png" /><figcaption>On failure destination gives a bit more option than dead-letter queue // source: AWS</figcaption></figure><h4>Think about multiple ways of triggering Lambda</h4><p>You can trigger Lambdas by events emitted from different sources. List of possible event sources is long.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w79kCdi0p-VZEkbcOWVsKg.png" /><figcaption>Apart from AWS services you can integrate your lambda with partners event sources (via EventBridge) // source: AWS</figcaption></figure><p>The most popular ways to trigger Lambda are:</p><ul><li><strong>SNS</strong> — pub/sub (push) functionality; one or many Lambdas send message to topic, which others Lambdas/services can be subscribed to;</li><li><strong>SQS</strong> — queues (pull) are ideal solution when you expect throttling problem — for example if your traffic is very dynamic and you would like to avoid to lose any messages, or if you want to optimize Lambda autoscaling (60 additional instances per minute to a maximum of 1,000 concurrent invocations);</li><li><strong>EventBridge</strong> (CloudWatch Events) — used for more complex events management, where you can filter by event patterns, subscribe to scheduled job (cron), 3rd party emitters and more — such as communication between accounts;</li><li><strong>Kinesis </strong>— dedicated for streaming or data driven applications;</li><li><strong>S3 </strong>— you can trigger Lambda based on changes in S3 bucket eg. one service upload file, other service does some operation on it;</li><li><strong>DynamoDB</strong> — Lambda can read records from DB stream, so you can react each time, when data changes;</li></ul><p>By default SNS and SQS don’t guarantee, that messages will be delivered in the same order, as they were published. Occasionally there could be situation that messages will be delivered more than once, so you might need deduplication mechanism. If you would like to prevent duplicate messages from delivered and need order to be guaranteed, think about <strong>SNS FIFO</strong> or <strong>SQS FIFO</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fdWrmrZxXuuxU580GbJz3g.jpeg" /><figcaption>Cheatsheet for choosing async event sources for Lambda // source: <a href="https://twitter.com/theburningmonk">@theburningmonk</a></figcaption></figure><h4>Get some inspirations from others</h4><p>There are many different ways to deal with events in AWS, so I encourage you to read about how others build theirs async architecture. Great source of patterns and solutions is <a href="https://aws.amazon.com/solutions/reference-architectures/">AWS Solutions Reference Architectures</a> and <a href="http://cdkpatterns.com/">cdkpatterns.com</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/951/1*LfLO7yFFfDw9jYuj1NtIjg.png" /><figcaption>The Big Fan is a popular pattern based on SNS and SQS (filtering to fan out events to different consumers) // source: cdkpatterns.com</figcaption></figure><h4>Understand Lambda execution to improve speed</h4><p>When you are Node.js developer, <strong>cold starts</strong> (increased invocation latency) might not be your main issue. If Lambda does a lot of work eg. soon after invocation (making connection to DB, retrieving data from SSM or so), you might want to improve that speed — especially if that could improve UX.</p><p>Good idea is to <strong>cache </strong>some data in memory, so it can be used in another invocation within the same <strong>execution environment</strong> (runtime) later. You can play with <a href="https://github.com/middyjs/middy">Middy</a> middleware (cache, ssm packages) or memoize some pure functions eg. with <a href="https://github.com/medikoo/memoizee">Memoizee</a>.</p><p>Think about <a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-concurrency.html">provisioned concurrency</a> (requested number of execution environments will be always prepared to respond to your function’s invocations), but be careful — it might be too much costly for some cases. If your traffic is very dynamic, you can <a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-concurrency.html#configuration-concurrency-provisioned">manage your provision concurrency</a> within Application Auto Scaling.</p><p>Learn about <a href="https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html#runtimes-lifecycle">Lambda execution environment lifecycle</a>. It might be good idea to run some logic in <strong>Init phase</strong> — out of the handler, so it can be run after starting runtime, eg. by provisioned concurrency, so <strong>before invocation</strong>. You can put into Init phase synchronous code, up to 10 seconds to compute. Async code in Node.js (such us loading SSM by SDK) might not be finished in Init phase (will be frozen and resume in first invocation). If you would like to finish loading data (eg. from SSM or DB) in Init phase, try Python.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9gwInqH1hc0IV6R-w9vjhg.png" /><figcaption>The lifecycle of the execution environment — there could be many invocations within the same runtime, so you might cache some data in global Node.js scope // source: AWS</figcaption></figure><p>Problem of async code in Node.js is not limited do Init phase only. If you run async code in Lambda, make sure that it’s finished before you will return response / resolve handler. The best way is to always keep async logic in <strong>async-await </strong>promises instead of callbacks. Callback pattern (such as using setTimeout) can lead to <a href="https://lumigo.io/blog/node-js-lambda-execution-leaks-a-practical-guide/">execution leaks</a> (problem, when executing code runs in a different invocation than the original execution context).</p><h4>Use Lambda layers</h4><p>You might be in situation, that one service share a lot of code with others. In Node.js you can have some common<strong> node_modules</strong> and it might not be good idea to include all of them in each bundle, or to deploy to each Lambda container separately. Think about deploy common code as <a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html">AWS Lambda layer</a>. It could be useful also if some part of your Lambdas code is heavy, and you would like to avoid deploying it each time, when you change something.</p><h3>DynamoDB — performance and throttling</h3><h4>Learn about difference between DynamoDB and relational DB</h4><p>Proper understanding<strong> NoSQL</strong> database is key to write performant queries. DynamoDB has been created to be fast and scalable. For someone who came from SQL world into DynamoDB, it might me hard to change the way of thinking about <strong>data schemas</strong>. I recommend read Alex DeBrie’s DynamoDB guide (<a href="https://www.dynamodbguide.com/">dynamodbguide.com</a>), <a href="https://www.alexdebrie.com/">blog</a> or buy “The DynamoDB Book” (<a href="https://www.dynamodbbook.com/">dynamodbbook.com</a>), which gives you deep knowledge about that database plus some great pattern to work with DynamoDB. Good summary of Dynamo DB you can find in <a href="http://serverlesslife.com/DynamoDB_Design_Patterns_for_Single_Table_Design.html">this article</a>.</p><h4>Think about access patterns</h4><p>DynamoDB is fast, because you access data directly from partition (you always need to know <strong>partition key</strong> and optionally <strong>sort key</strong>). When you query for more than one item, it’s important to have proper keys design. One key can be <strong>composition</strong> of different data. It’s common practice to give simple name to keys (as <strong>PK</strong>, <strong>SK) </strong>and keep different types of data in the same table (<strong>single table design</strong>). You can different that data types by adding prefix to your key followed by hash, such as <strong>TYPE#123</strong>. Because data in DynamoDB are organized in B-trees, you should also think about, how you would like to sort / filter it, and then design your sort key accordingly. If you would like to access your data in couple different ways, consider using <strong>GSI </strong>or <strong>LSI</strong> (global / local secondary index).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1xw4SN2YOQvubjWEbqSv2w.png" /><figcaption>Single table design is a great pattern to deal with Dynamo DB // source: alexdebrie.com</figcaption></figure><h4>Keep in mind DynamoDB limits</h4><p>DynamoDB <a href="http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html">has some limits</a>, which you should always keep in mind:</p><ul><li>max size for single item is 400KB</li><li>max result size for Scan/Query operation is 1MB</li><li>batch get: max 25 items, 16MB; batch put: max 100 items, 16MB</li><li>3000 RCU (Read Capacity Units) and 1000 WCU (Write Capacity Units) throughput limit per partition</li><li>40 000 RCU and WCU default limit per table, 80 000 per account</li><li>max 10GB per collection with the same partition key (doesn’t include global secondary indexes)</li></ul><h4>Distribute traffic</h4><p>If your application has big traffic or operate on large payloads, you might hit throughput limits. That’s why it’s important to have <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-partition-key-uniform-load.html">proper design for your partition keys</a>, to avoid situation, when one partition has much more traffic than others (<strong>“hot” partition key</strong>). If you struggle problems with proper design, you can try distribute writes across more partitions by <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-partition-key-sharding.html">sharding</a>.</p><h4>Handle errors</h4><p>DynamoDB should be reliable, especially if you’re using <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GlobalTables.html">Global Tables</a>, but don’t forget about handling errors — log them, think about retry logic and set DLQ for your Lambda.</p><p>If you’re using AWS SDK, you will have <strong>retry</strong> and exponential backoff algorithms set by default. It is useful, as SDK will retry request in case of limits /throttling issues or 5xx errors.</p><p>Situation is a bit more complicated, when you are using <strong>batch methods</strong> (BatchGetItem, BatchWriteItem). As some operations can failed (eg. because of throttling issues) and some succeed in the same request - you will receive 200 response and SDK won’t retry that request. You will need to write your own retry / backoff mechanism to handle<strong> UnprocessedKeys / UnprocessedItems</strong> if there any in response.</p><p><a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.Errors.html">Read more about error handling in DynamoDB.</a></p><h3>Others</h3><h4>Do not forget about monitoring</h4><p>If you are Node.js developer, you might be interested in <a href="https://getpino.io/">pino</a> logger. It has a lot of helpful functionalities, such as redaction (if you would like to avoid showing sensitive data in your logs), levels (eg. to do not show debug logs on prod) or child loggers — to add common data for each logs.</p><p>Proper <strong>logging</strong> (eg. payloads, ids, states, errors) will help you <strong>debug</strong> your code (eg. building queries in CloudWatch Logs Insights). This is especially important in event driven application, when you cannot debug whole workflow within one trace id.</p><p>You should also consider <a href="https://docs.aws.amazon.com/lambda/latest/dg/monitoring-metrics.html">create alarms for metrics</a>, which are important to you. For example <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Create_Anomaly_Detection_Alarm.html">creating</a> <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Anomaly_Detection.html">CloudWatch Anomaly Detection</a> for lambda invocations sounds interesting.</p><p>Do not forget about creating charts, diagrams and others statistics. In CloudWatch you can create dashboards, but think about third party solutions. There are a lot of services, which work well with AWS and make your debugging, tracking performance much easier — eg. New Relic, Datadog or Sumo Logic.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8vs1Z5b026cwf_lcla8kzA.png" /><figcaption>Creating charts in 3rd party solutions might be easier // source: New Relic Documentation</figcaption></figure><h4>Do not overcomplicate your stack</h4><p>Your services can grow quickly. You might think to put all functions / resources definitions into one CloudFormation <strong>stack</strong> (eg. in single serverless.yaml /<a href="https://www.serverless.com/">Serverless</a> framework). As this sounds good for few functions (which together delivers single feature), in future it could make you some troubles.</p><p>For a long time CloudFormation had limit of 200 resources in stack and because one function needs couple of resources (Lambda, versions, roles etc.), there was high risk, that you will hit that limit quickly. Especially if you keep common resources (such as S3, DynamoDB, SNS, SQS) in the same stack. Now that limit has been increased to <strong>500</strong>, but still —you should avoid keeping so many resources in one stack.</p><p>If you hit that limit, <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/refactor-stacks.html">moving resources from one stack to another</a> can be painful — you will need to guarantee, that during operation all yours resources will be reliable. AWS supports importing resources from one stack to another, but it’s not easy to set, especially if you use framework, such as Serverless, to generate and deploy CloudFormation stack.</p><p>In summary:</p><ul><li>think about resources limit at the beginning, to avoid refactoring problems;</li><li>splitting architecture into multiple stacks might improve you build / deploy process;</li><li>keep you global / common resources (database, queues, topics) out of stacks, which you often deploy;</li><li>if you really need to keep all resources in single CoudFormation stack, think about <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-nested-stacks.html">nested stacks</a>.</li></ul><h4>Choose the best framework for you</h4><p>If you are new in Serverless and want to create something simple, think about <a href="https://aws.amazon.com/amplify/">AWS Amplify</a>.</p><p>For larger project it’s worth to try <a href="https://www.serverless.com/">Serverless</a> framework, which improve building and deploying Lambda services (and surrounding resources). It has large community, and a lot of plugins (eg. <a href="https://www.serverless.com/plugins/serverless-webpack">Webpack</a> or <a href="https://www.serverless.com/plugins/serverless-offline/">Offline</a>).</p><p>Serverless framework supports different providers, but you might think about something dedicated only for AWS — try <a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html">Serverless Application Model</a>.</p><p>You should also consider <a href="https://www.terraform.io/">Terraform</a>, which is great framework for managing <strong>persistent shared infrastructure</strong>. It’s common practice to use Terraform for cloud infrastructure and SAM or Serverless for deploying applications.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XaLfZlvPVfBt2ox5G8OkPg.png" /><figcaption>Common practice is to use Terraform to manage shared infrastructure &amp; Serverless or SAM to deploy apps. AWS Amplify is interesting for beginners and for simple or mobile apps. You can also try AWS SDK without touching any framework.</figcaption></figure><h4>Listen podcasts, read newsletters, prepare for certificate</h4><p>My favorite resource is <a href="https://offbynone.io/">Off-by-none</a> newsletter + podcasts: <a href="https://aws.amazon.com/podcasts/aws-podcast/?podcast-list.sort-by=item.additionalFields.EpisodeNum&amp;podcast-list.sort-order=desc">AWS Podcast</a>, <a href="https://www.serverlesschats.com/">Serverless Chats</a>, <a href="https://www.realworldserverless.com/">Real World Serverless</a>.</p><p>Very useful informations can be found on <a href="https://www.youtube.com/channel/UCd6MoB9NC6uYN2grvUNT-Zg">AWS YouTube channel</a>, <a href="https://aws.amazon.com/solutions/reference-architectures/">AWS Solutions Reference Architectures</a> and <a href="http://cdkpatterns.com/">cdkpatterns.com</a>.</p><p>Some of Twitter accounts, which are worth to follow: <a href="http://twitter.com/jeremy_daly">@jeremy_daly</a>, <a href="https://twitter.com/sheenbrisals">@sheenbrisals</a>, <a href="https://twitter.com/danilop">@danilop</a>, <a href="https://twitter.com/dabit3">@dabit3</a>, <a href="https://twitter.com/theburningmonk">@theburningmonk</a>, <a href="https://twitter.com/jbesw">@jbesw</a>, <a href="https://twitter.com/houlihan_rick">@houlihan_rick</a>, <a href="https://twitter.com/jeffbarr">@jeffbarr</a>, <a href="https://twitter.com/alexbdebrie">@alexbdebrie</a>, <a href="https://twitter.com/awsgeek">@awsgeek</a>.</p><p>For beginner great resource is <a href="https://serverless-stack.com/">serverless-stack.com</a>.</p><p>If you would like to be AWS architect, you can prepare for certificate. I can recommend <a href="https://www.udemy.com/course/aws-certified-solutions-architect-associate-saa-c02/">Ultimate AWS Certified Solutions Architect Associate </a>course.</p><p>Exploring serverless world has opened my eyes on problems / areas, which I still need to learn. This was just small list of tips, but hope you found something useful.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7db5d91ecfc4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Serverless PDF news renderer in Node.js]]></title>
            <link>https://machnicki.medium.com/serverless-pdf-news-renderer-in-node-js-138223a4557a?source=rss-2bdfb43ca071------2</link>
            <guid isPermaLink="false">https://medium.com/p/138223a4557a</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[s3]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Miron Machnicki]]></dc:creator>
            <pubDate>Fri, 03 May 2019 05:35:14 GMT</pubDate>
            <atom:updated>2019-05-03T05:53:51.893Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*REZqgmV2NUxKb_7xvpxTJQ.png" /></figure><p>Engineers spend a lot of time debugging and looking for performances bottlenecks. Memory leaks, split brains issues… You need to know, how to write good code but also predict which parts of service will need more power and eventually move them into separate microservice or create serverless function (eg. AWS Lambda).</p><h4>PDF renderer engine is good case for using AWS Lambda, because it uses a lot of memory in comparison to the rest of my application. I want to share with you implementation of PDF news app which I have created based on AWS: EC2, S3, DynamoDB, Lambda.</h4><h3>Concept</h3><p>Application renders news in PDF for chosen country and category. As a source of news I use <strong>newsapi.org</strong> REST endpoint. UI is simple React app and server is created by Express.js. <strong>Source code: </strong><a href="https://github.com/machnicki/aws-pdf/tree/version/1"><strong>https://github.com/machnicki/aws-pdf/tree/version/1</strong></a></p><h3>Workflow</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RudP82tjP3_V4VCcV4HckQ.png" /></figure><ol><li>Before Express API will call newapi.org, first it will look for proper document in S3 . If there is PDF already generated, API will return it instead of create fresh one— simple 1-hour cache mechanism. App creates only one PDF for particular combination of time (date and hour), category and country. <strong><em>${country}-${category}-${date}.pdf </em></strong>is unique key for document and file name.</li><li>Express API calls newapi.org with proper category and country. Response is in JSON format.</li><li>After receive news in JSON, Express API generates HTML document based on that JSON data and HTML template (template is kept in DynamoDB). Templating is done via handlebars (<a href="https://handlebarsjs.com/">https://handlebarsjs.com/</a>). Generated HTML document is saved into DynamoDB.</li><li>Express API invokes Lambda function, passing file name (unique key for HTML document) and expecting url for generated PDF as response.</li><li>Lambda function gets proper documents HTML from DynamoDB.</li><li>Lambda function generates PDF (I use <strong>html-pdf </strong>node module: <a href="https://github.com/marcbachmann/node-html-pdf">https://github.com/marcbachmann/node-html-pdf</a>) and saves it into S3.</li><li>As response Lambda function returns url to generated PDF document.</li></ol><h3>React SPA</h3><p>For UI I have used <a href="https://github.com/facebook/create-react-app">create-react-app</a> with <a href="https://github.com/react-bootstrap/react-bootstrap">react-bootstrap</a>. All frontend code you can find here: <a href="https://github.com/machnicki/aws-pdf/tree/version/1/src">https://github.com/machnicki/aws-pdf/tree/version/1/src</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9JSqEU5vdY8iuUOLfc53vw.png" /><figcaption>Simple UI based on bootrstap</figcaption></figure><p>For purposes of this article, important part is, how javascript use HTML form data, send them to API and open generated PDF in new window: <a href="https://github.com/machnicki/aws-pdf/blob/version/1/src/App.js#L33-L53">https://github.com/machnicki/aws-pdf/blob/version/1/src/App.js#L33-L53</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bT1yBhwSwwzmq217xKi6Cw.png" /><figcaption>UI sends country and category to API and open new window with received url (PDF)</figcaption></figure><h3>Express backend</h3><p>I decided to work with newest ES syntax with experimental ES modules (node version 11). Thats why I need to use <strong>— experimental-modules</strong> flag to run my nodejs server: <a href="https://github.com/machnicki/aws-pdf/blob/version/1/package.json#L20">https://github.com/machnicki/aws-pdf/blob/version/1/package.json#L20</a>.</p><p>I have defined couple simple API endpoint, most important is POST /api which triggers generating PDF based on UI payload: <a href="https://github.com/machnicki/aws-pdf/blob/version/1/api/index.mjs#L21">https://github.com/machnicki/aws-pdf/blob/version/1/api/index.mjs#L21</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*z_JwUdsJlchgcNXyTgcSgQ.png" /></figure><p>Other endpoints are not part of this article, they are used to edit template, download list of documents or particular document. You can explore it yourself, enjoy!</p><p>Main <strong>GET / </strong>route is used to serve UI (index.html). Depends on environment, this is webpack dev server or static build.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QAisiQzXFGZIvm1ko6_TKg.png" /></figure><h4>DynamoDB vs. DocumentDB vs. S3 store</h4><p>In my application I need to store documents HTML code to consume it by Lambda function. It’s massive string so I cannot just pass it as payload. I have decided to use NoSQL database. <strong>DynamoDB</strong> was simple enough, but I have considered another NoSQL database from Amazon — <strong>DocumentDB</strong>, which gives me MongoDB and possibility to save larger data (16MB for document, where MongoDB max size of item is 400KB — for large HTML code it could be not enough). At this moment I stick with DynamoDB.</p><p>Documents are kept in simple pair: id (file name) and html (base64 HTML). Template used for generating document is specific document with name “template”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4zBcB6fHDevpXCsyF6E4Eg.png" /><figcaption>Template is document with hardcoded id</figcaption></figure><p>All code which is run do create DB structure you can find here: <a href="https://github.com/machnicki/aws-pdf/blob/version/1/api/db/createTable.mjs">https://github.com/machnicki/aws-pdf/blob/version/1/api/db/createTable.mjs</a></p><p>In future I will probably resign from DynamoDB and keep all data directly in S3. S3 will inform Lambda to create new PDF if S3 will receive HTML document. I consider <strong>S3 Select</strong> or <strong>Amazon Athena</strong> tools.</p><h3>EC2, DynamoDB, Lambda via LocalStack</h3><h4>Create HTML document and save in DB</h4><p>My <strong>POST /api</strong> endpoint gets country and category as parameters and calls newapi.org for news in JSON format. Based on that data and HTML template (which was crated in previous chapter) I create HTML document (using <a href="https://github.com/wycats/handlebars.js">handlebars</a> node module) and save that document into DynamoDB. Next I call Lambda function, passing file name (id of HTML document in DynamoDB) and expect url of PDF document (PDF is generated and save into S3 by Lambda — explained later).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XA144OEnbk3pZiMS6nC9UA.png" /><figcaption>getPDF is called on POST /api express route</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KPeQHEmEFIhAKGfIPW2F1g.png" /><figcaption>pdf-generator.mjs</figcaption></figure><p>Source code: <a href="https://github.com/machnicki/aws-pdf/blob/version/1/api/pdf.mjs">https://github.com/machnicki/aws-pdf/blob/version/1/api/pdf.mjs</a> and <a href="https://github.com/machnicki/aws-pdf/blob/abf5f072f04d5bb680708186fbf237f54d1fb225/api/pdf-generator.mjs">https://github.com/machnicki/aws-pdf/blob/abf5f072f04d5bb680708186fbf237f54d1fb225/api/pdf-generator.mjs</a> (invoking AWS Lambda)</p><h4>LocalStack</h4><p>I was looking for solution, where I could write all my serverless code locally before I will deploy it into AWS.</p><p><a href="https://github.com/localstack/localstack"><strong>LocalStack</strong></a><strong> </strong>gives me local version of S3, DynamoDB and Lambda.</p><p><a href="https://github.com/localstack/localstack">GitHub - localstack/localstack: 💻 A fully functional local AWS cloud stack. Develop and test your cloud &amp; Serverless apps offline</a></p><p>To work with all services locally (so with Lambda as well) you need to run LocalStack via docker (as I have described in README: <a href="https://github.com/machnicki/aws-pdf/tree/version/1#aws-lambda-pdf-news-generator">https://github.com/machnicki/aws-pdf/tree/version/1#aws-lambda-pdf-news-generator</a>).</p><p>All services are run or different ports and each Lambda function on different containers.</p><p>In config file you need to use <strong>LOCALSTACK_HOSTNAME</strong> env variable instead of localhost to allow those containers talk each other.</p><p>Check my config file <a href="https://github.com/machnicki/aws-pdf/blob/version/1/api/config.mjs">https://github.com/machnicki/aws-pdf/blob/version/1/api/config.mjs</a> which returns different values depends on environment. For production (AWS) I don’t need to specify endpoint parameters for each service. Additionally for Lambda environment I don’t need so specify accessKeyId and secretAccessKey (Lambda permissions are configured in AWS console).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IDvkUbrg97k6nFNUaTT6Jw.png" /><figcaption>Config behaves differently for lambda, production and local environments</figcaption></figure><h3>Lambda</h3><p>My lambda (<a href="https://github.com/machnicki/aws-pdf/blob/version/1/lambda/pdf.mjs">https://github.com/machnicki/aws-pdf/blob/version/1/lambda/pdf.mjs</a>) apart of source code needs also something which will convert HTML into PDF. I have decided to use <a href="https://github.com/marcbachmann/node-html-pdf">html-pdf</a> node module, which is based on phantomjs.</p><p>Lambda function as a parameter retrieve file name, load HTML document from DynamoDB, generate PDF, save it into S3 and returns url to that PDF:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*63QgZ_673kCVE76SI9So3w.png" /><figcaption>My lambda function</figcaption></figure><h4>Generator</h4><p>For most cases<strong> Serverless </strong>framework is easy and quick way to develop Lambda functions, but in my code I rely only on <strong>aws-sdk</strong> node module.</p><p>This is what I’ve needed:</p><ul><li>write functions in Node.js 11 with experimental ES modules enabled,</li><li>deploy large phantomjs binary file (for generating PDFs),</li><li>depends on environment, deploy function to AWS or to LocalStack.</li></ul><h4>Webpack bundler</h4><p>AWS Lambda supports only 6 and 8 versions of Node.js.</p><p>I have decided to use Webpack to create one bundle file which will be readable by AWS Lambda.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nXo8NcoM_WPR0sxsMd09aA.png" /><figcaption>Function which returns one bundle file as source code (string)</figcaption></figure><h4>Phantomjs binary</h4><p>My AWS Lambda creator (<a href="https://github.com/machnicki/aws-pdf/blob/version/1/lambda/create.mjs">https://github.com/machnicki/aws-pdf/blob/version/1/lambda/create.mjs</a>) can create zip file based source file of Lambda function with additional files (like phantomjs binary).</p><p>Lambda package size can be maximum 50MB. For larger zip file then 10MB it’s recommended to upload them first to S3 and then point Lambda to that file.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0ImA_jO3Nj2SQBiDbTZVOA.png" /><figcaption>For larger zip files its better to upload them to S3 first</figcaption></figure><p>I have discovered that its better to generate zip file by using command line instead of node module node-native-zip, which works fine for simple functions, not necessary for large files, like phantomjs binary (50MB and more).</p><p><strong>If you will struggle issues with zip generator in my code, try use another one. Command line is probably good choice.</strong></p><p>At the end your zip file which you will deploy to AWS Lambda should include 3 files:</p><ul><li>PDFGenerator.js (commonjs bundle — main function)</li><li>phantomjs (binary — you need to download linux version to run it in AWS Lambda environment, eg. form <a href="https://bitbucket.org/ariya/phantomjs/downloads/">https://bitbucket.org/ariya/phantomjs/downloads/</a>)</li><li>scripts/pdf_a4_portrait.js (needed by html-pdf node module to generate A4 format)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cgS3xDGcU6o5cy31pS1CCA.png" /><figcaption>This is how I generate zip file with all 3 necessary files. You can generate zip file manually from command line.</figcaption></figure><p>In README (<a href="https://github.com/machnicki/aws-pdf/blob/version/1/README.md">https://github.com/machnicki/aws-pdf/blob/version/1/README.md</a>) you will find necessary informations, how to run application locally.</p><p>Hope you have enjoyed this article. In near future I will try to use other AWS tools and refactor current code:</p><ul><li>Make sure that only PDF creator has access to PDF (authentication)</li><li>Move rest of the app from EC2 into Lambda</li><li>Replace DynamoDB (as a store for html code) by S3 events</li><li>Performance tests and improvements</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=138223a4557a" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>