<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oleksandr Hanhaliuk</title>
    <description>The latest articles on DEV Community by Oleksandr Hanhaliuk (@ohanhaliuk).</description>
    <link>https://dev.to/ohanhaliuk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1285174%2F5082fbbd-bc6a-41a9-b749-bf74e3e810cb.png</url>
      <title>DEV Community: Oleksandr Hanhaliuk</title>
      <link>https://dev.to/ohanhaliuk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ohanhaliuk"/>
    <language>en</language>
    <item>
      <title>Scaling AWS FIFO SQS (queues) without blocking customers</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Mon, 16 Mar 2026 20:14:30 +0000</pubDate>
      <link>https://dev.to/ohanhaliuk/scaling-aws-fifo-sqs-queues-without-blocking-customers-1noi</link>
      <guid>https://dev.to/ohanhaliuk/scaling-aws-fifo-sqs-queues-without-blocking-customers-1noi</guid>
      <description>&lt;p&gt;Let’s imagine you have two services (for example, on &lt;strong&gt;AWS Lambda&lt;/strong&gt;). Service 1 communicates with Service 2 via commands: messages in FIFO SQS (&lt;a href="https://codeopinion.com/commands-events-whats-the-difference/" rel="noopener noreferrer"&gt;commands VS events&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FIFO&lt;/strong&gt; (First-In-First-Out) is necessary in this situation to ensure the order of command processing in Service 2 (regular AWS SQS does not guarantee the order of the message queue, and causes duplication).&lt;/p&gt;

&lt;p&gt;To achieve parallelism, you can use message groups (&lt;strong&gt;message group ID&lt;/strong&gt;), where each group is processed sequentially, but several groups are processed in parallel. This allows you to have separate “mini-queues” for different message sources, for example, for each customer.&lt;/p&gt;

&lt;p&gt;Let’s imagine you have &lt;strong&gt;2 customers&lt;/strong&gt;, for each of which you transfer messages from &lt;strong&gt;Service 1&lt;/strong&gt; to &lt;strong&gt;Service 2&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Customer &lt;em&gt;A: 1,000 commands per hour.*Customer *B: 500,000 commands per hour.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Each of them has its own &lt;strong&gt;message group ID&lt;/strong&gt; (which ensures that they do not block each other). Service 2 can process approximately &lt;strong&gt;100,000 commands&lt;/strong&gt; per hour. Thus, Customer A’s messages should, in theory, be processed within that hour, as they are not blocked.&lt;/p&gt;

&lt;p&gt;This would indeed be the case if it weren’t for the AWS FIFO SQS limitation: the queue only looks at the first &lt;strong&gt;120,000&lt;/strong&gt; messages (until &lt;a href="https://aws.amazon.com/about-aws/whats-new/2024/11/amazon-sqs-increases-in-flight-limit-fifo-queues/" rel="noopener noreferrer"&gt;recently&lt;/a&gt; it was &lt;strong&gt;20,000&lt;/strong&gt;) to find available groups for processing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyqb4kgdxj8at4yxy2z4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyqb4kgdxj8at4yxy2z4.png" alt="AWS FIFO SQS LIMIT&amp;lt;br&amp;gt;
" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Solution&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In this case, it is necessary to create an architecture that will not allow one Customer to block another while maintaining the conditional order of messages. There are several solutions to this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Store messages in additional storage if SQS has more than X messages in the queue&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make the processing speed on Service 2 faster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dynamically create an additional FIFO SQS for a group of messages with high traffic&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will describe the &lt;strong&gt;last&lt;/strong&gt; scenario below, as I have experience creating this type of architecture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxc4h7gq5euvknw4kna5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxc4h7gq5euvknw4kna5.png" alt="Arhitecture description" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Architecture description&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tracking SQS load:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Each message adds a counter in &lt;strong&gt;DynamoDB&lt;/strong&gt; for the corresponding &lt;strong&gt;userId&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After processing the message, &lt;strong&gt;Service 2&lt;/strong&gt; sends an &lt;strong&gt;SNS&lt;/strong&gt; message about the completion of processing, to which &lt;strong&gt;Service 1&lt;/strong&gt; is subscribed — the counter in &lt;strong&gt;DynamoDB&lt;/strong&gt; decreases.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Separate queue allocation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If a Customer exceeds X (e.g., &amp;gt; 100,000) active messages, a separate &lt;strong&gt;FIFO&lt;/strong&gt; SQS is created for it (using &lt;strong&gt;AWS SDK SQS Client&lt;/strong&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All new messages from this Customer are redirected to this queue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A separate trigger for &lt;strong&gt;Lambda&lt;/strong&gt; is added to &lt;strong&gt;Service 2&lt;/strong&gt;, which processes this queue (using &lt;strong&gt;AWS SDK&lt;/strong&gt; Lambda client). PS: this fact violates the SoC principle, since the trigger for &lt;strong&gt;Lambda&lt;/strong&gt; belongs to &lt;strong&gt;Service 2&lt;/strong&gt;. However, you can optimise the architecture and create a separate service for monitoring &lt;strong&gt;SQS&lt;/strong&gt; and creating triggers for &lt;strong&gt;Lambda&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Cleaning&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;All records in &lt;strong&gt;DynamoDB&lt;/strong&gt; have a TTL of 1 day.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After it expires, the record is deleted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The deletion event triggers Lambda&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The newly created FIFO SQS queue and its Lambda trigger are deleted.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Real-time SQS load monitoring&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Guaranteed non-blocking for &lt;strong&gt;customers&lt;/strong&gt; with moderate traffic&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An additional &lt;strong&gt;DynamoDB&lt;/strong&gt; database and business logic on &lt;strong&gt;Lambda&lt;/strong&gt; requires implementation, maintenance, and costs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simpler implementations based on &lt;strong&gt;CloudWatch Alarms&lt;/strong&gt; are possible, but they will have a delay in creating additional &lt;strong&gt;AWS FIFO SQS&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Violation of the &lt;strong&gt;SoC&lt;/strong&gt; (&lt;strong&gt;Separation of Concerns&lt;/strong&gt;) principle: because &lt;strong&gt;Service 1&lt;/strong&gt; creates Lambda triggers in &lt;strong&gt;Service 2&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you have a system where different customers can have very different loads, but you need to maintain message order, it is important to have a mechanism for dynamic traffic separation. The &lt;strong&gt;FIFO limit of 120,000&lt;/strong&gt; messages is not obvious but &lt;strong&gt;&lt;em&gt;critical&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This approach ensures stability and independence between &lt;strong&gt;Customers&lt;/strong&gt; even during peak periods.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>cloudcomputing</category>
      <category>awscommunity</category>
    </item>
    <item>
      <title>I Built a “AWS CDK NodejsFunction”… but for AWS Fargate</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Sat, 14 Feb 2026 17:36:41 +0000</pubDate>
      <link>https://dev.to/aws-builders/i-built-a-aws-cdk-nodejsfunction-but-for-aws-fargate-97i</link>
      <guid>https://dev.to/aws-builders/i-built-a-aws-cdk-nodejsfunction-but-for-aws-fargate-97i</guid>
      <description>&lt;p&gt;Deploying a NodeJS application on a Fargate shouldn’t be harder than deploying it on Lambda. But it usually does.&lt;/p&gt;

&lt;p&gt;I worked with ECS Fargate for years and I know the pain: Dockerfiles, ECR, task definition, container definition, IAM roles, networking, logging, autoscaling. None of it is wrong, but its just repeating boilerplate. You can find full code example of Fargate Service on NodeJS here.&lt;/p&gt;

&lt;p&gt;At the same time for Lambda its pretty simple — you just use package AWS provided for us &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This frustration led me to build fargate-nodejs — open source abstraction that helps developers create Fargate NodeJS service with just one Construct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idea: Lambda Style, Fargate Runtime
&lt;/h2&gt;

&lt;p&gt;The core idea behind fargate-nodejs is simple:&lt;/p&gt;




&lt;p&gt;&lt;em&gt;You provide a Node.js entry file&lt;br&gt;
The construct creates a production-ready Fargate service&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;No Dockerfile, no manual writing of ECS resources.&lt;br&gt;
The goal is to hide most repetitive decision.&lt;/p&gt;
&lt;h2&gt;
  
  
  High-Level Architecture
&lt;/h2&gt;

&lt;p&gt;Here’s how the construct works internally (simplified):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your Node.js code is bundled using esbuild&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbkd34bwn19g893zftdpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbkd34bwn19g893zftdpa.png" alt="Fargate NodeJS Service"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A minimal container image is created automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8hs7d90w2ru3cvnfvar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8hs7d90w2ru3cvnfvar.png" alt="ESBuild"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An ECS task definition and Fargate service are generated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foq40an9cx4j1x8yiu0p7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foq40an9cx4j1x8yiu0p7.png" alt="Cluster and task definition"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IAM roles and CloudWatch logs are configured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipf7rms8gwkmleegbtf2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipf7rms8gwkmleegbtf2.png" alt="IAM Roles and Log group"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optional integrations: Application Load Balancer (for HTTP services), Autoscaling, SQS-based workers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is standard AWS infrastructure, just created through a higher-level abstraction.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example: HTTP Service on Fargate
&lt;/h2&gt;

&lt;p&gt;A minimal Express app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import express from 'express';

const app = express();

app.get('/', (_, res) =&amp;gt; {
  res.send('Hello from Fargate');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { FargateNodejsService } from 'fargate-nodejs'

new FargateNodejsService(this, 'MyService', {
  entry: './src/index.ts',
  containerPort: 3000,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When This Abstraction Makes Sense
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This approach works best when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda feels too restrictive (timeouts, execution model)&lt;/li&gt;
&lt;li&gt;You want long-running Node.js processes&lt;/li&gt;
&lt;li&gt;Docker feels like unnecessary overhead&lt;/li&gt;
&lt;li&gt;You’re already using AWS CDK&lt;/li&gt;
&lt;li&gt;You value consistency and developer experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It’s not intended for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;very custom container setups&lt;/li&gt;
&lt;li&gt;complex, hand-tuned ECS clusters&lt;/li&gt;
&lt;li&gt;Kubernetes-style workflows
The abstraction is intentionally opinionated.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Full Walkthrough Video
&lt;/h2&gt;

&lt;p&gt;I recorded a full video walkthrough where I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;walk through the code&lt;/li&gt;
&lt;li&gt;show the deployment&lt;/li&gt;
&lt;li&gt;and inspect what AWS actually creates in the console&lt;/li&gt;
&lt;li&gt;test few use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Watch the full video: &lt;br&gt;


  &lt;iframe src="https://www.youtube.com/embed/LMTDykz6NuI"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source &amp;amp; Feedback
&lt;/h2&gt;

&lt;p&gt;The project is open source, and feedback is welcome:&lt;/p&gt;

&lt;p&gt;GitHub repo: &lt;a href="https://github.com/alexsanteenodev/fargate-nodejs" rel="noopener noreferrer"&gt;https://github.com/alexsanteenodev/fargate-nodejs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NPM package: &lt;a href="https://www.npmjs.com/package/fargate-nodejs" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/fargate-nodejs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>cloudnative</category>
      <category>npm</category>
    </item>
    <item>
      <title>AWS Bedrock Magic Summarizes Any Video Fast!</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Sun, 03 Aug 2025 16:45:06 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-bedrock-magic-summarizes-any-video-fast-gk3</link>
      <guid>https://dev.to/aws-builders/aws-bedrock-magic-summarizes-any-video-fast-gk3</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/qxrCvH_FVY0"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>aws</category>
      <category>llm</category>
      <category>bedrock</category>
      <category>serverless</category>
    </item>
    <item>
      <title>AWS MCP and Cursor IDE Together Make AWS Simple!</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Sun, 08 Jun 2025 14:28:27 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-mcp-and-cursor-ide-together-make-aws-simple-50ik</link>
      <guid>https://dev.to/aws-builders/aws-mcp-and-cursor-ide-together-make-aws-simple-50ik</guid>
      <description>&lt;p&gt;I show how to connect AWS’s new Model Context Protocol (MCP) to Cursor IDE and cut a 65-line CDK stack down to 12 lines.&lt;br&gt;
In 8 minutes you’ll see:&lt;br&gt;
 • What MCP is in plain terms&lt;br&gt;
 • How to hook AWS MCP into Cursor IDE in 2 steps&lt;br&gt;
 • Side-by-side code before VS after MCP&lt;br&gt;
 • Bonus: AWS Cost Estimation using AI and AWS MCP&lt;/p&gt;

&lt;p&gt;If you use AWS, this is worth a quick watch.&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/htSGzFgTi5Q"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>serverless</category>
      <category>awscommunity</category>
    </item>
    <item>
      <title>How to Reduce Your AWS Bill: 3 Hidden Costs You Might Be Ignoring</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Fri, 28 Mar 2025 12:06:50 +0000</pubDate>
      <link>https://dev.to/aws-builders/how-to-reduce-your-aws-bill-3-hidden-costs-you-might-be-ignoring-424o</link>
      <guid>https://dev.to/aws-builders/how-to-reduce-your-aws-bill-3-hidden-costs-you-might-be-ignoring-424o</guid>
      <description>&lt;p&gt;I just published a quick  YouTube video showing 3 easy fixes to lower your AWS costs on production workloads:&lt;/p&gt;

&lt;p&gt;✅ Disable AWS X-Ray globally using Service Control Policies (SCP)&lt;br&gt;
✅ Prevent unwanted Lambda logging to CloudWatch&lt;br&gt;
✅ Reduce NAT Gateway costs with free VPC Endpoints for AWS services&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/RQjh_JRZqqU"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awscommunity</category>
      <category>cloud</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Beat The Burst: Optimizing AWS ALB and ECS Fargate for Sudden Traffic Spikes</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Tue, 18 Mar 2025 21:25:37 +0000</pubDate>
      <link>https://dev.to/aws-builders/beat-the-burst-optimizing-aws-alb-and-ecs-fargate-for-sudden-traffic-spikes-44h1</link>
      <guid>https://dev.to/aws-builders/beat-the-burst-optimizing-aws-alb-and-ecs-fargate-for-sudden-traffic-spikes-44h1</guid>
      <description>&lt;p&gt;AWS ECS Fargate is a serverless tool for managing your containers without the need to manage infrastructure. It is a very nice combination of configurable infrastructure that allows you to choose various CPU and memory numbers, provides robust autoscaling, and requires almost zero knowledge of DevOps.&lt;/p&gt;

&lt;p&gt;However, in high-traffic systems with high bursts of traffic, autoscaling of Fargate can be slow, and this might lead to overloading of existing containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Imagine the following scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have an Application Load Balancer with a target group which targets to ECS service&lt;/li&gt;
&lt;li&gt;ECS service has Fargate, which serves customers with a specific response&lt;/li&gt;
&lt;li&gt;Fargate tasks contain an application that consumes a lot of resources (CPU and memory).&lt;/li&gt;
&lt;li&gt;You have autoscaling based on CPU or connection per target or Task memory usage&lt;/li&gt;
&lt;li&gt;When traffic suddenly spikes, the CloudWatch Alarm triggers Autoscaling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Seems like a very common scenario for web applications, right? Nothing wrong here.&lt;/p&gt;

&lt;p&gt;Hoverer when CloudWatch alarm triggers scaling to time Fargate task scaled and become healthy can be few minutes.&lt;/p&gt;

&lt;p&gt;During this time, your existing tasks might be overloaded, which can lead to 503 responses to customers.&lt;/p&gt;

&lt;p&gt;We don’t want that, right?&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;There are multiple solutions to this problem. Some of them we will cover in the next articles. In this article, we will cover the solution that allows traffic to be offloaded to Lambda during traffic spikes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Focfkx92g19t79huer3mk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Focfkx92g19t79huer3mk.png" alt="AWS Fargate Autoscaling diagram" width="800" height="799"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create Lambda, which will serve traffic and have the same business as your Fargate container&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create an additional target group and point it to the Lambda&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy37zadjiis26jf2vc6r9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy37zadjiis26jf2vc6r9.png" alt="Lambda failover" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a target group to your Application Load Balancer listener:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EC2 -&amp;gt; Load balancers -&amp;gt; YourALB -&amp;gt; HTTPS:443 listener -&amp;gt; Edit listener -&amp;gt; Add target group&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create CloudWatch metrics which you want to rely on for traffic offload. It can be the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of connections per target&lt;/li&gt;
&lt;li&gt;Average target response time&lt;/li&gt;
&lt;li&gt;ECS metrics, like CPU, Memory usage, etc.&lt;/li&gt;
&lt;li&gt;Try to configure metrics that predict traffic spikes as early as possible. For example, if the number of connections per target is increasing, we can suspect that the traffic spike is close.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trigger Lambda on Cloud watch alarm&lt;/p&gt;

&lt;p&gt;Previously, you would need to send an SNS message from an Alarm and trigger Lambda from SNS. However, recently, AWS introduced a new feature that allows you to trigger Lambda from alarms directly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In your Lambda, write an application that updates the ALB listener weight using the AWS SDK or API (see &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_ModifyListener.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;). Add some traffic weight to the target group, which is configured in Step 1.&lt;/p&gt;

&lt;p&gt;This will offload traffic from Fargate and give existing tasks some space until new tasks are Autoscaled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Additionally, you can control Fargate autoscaling from this same Lambda in order to avoid conflicts with default scaling metrics. To do this, you can use SDK:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
  ECSClient,
  UpdateServiceCommand,
  DescribeServicesCommand,
  DescribeServicesCommandInput,
} from '@aws-sdk/client-ecs'


const describeServiceCommand = new DescribeServicesCommand({
    services: [ecsService],
    cluster: ecsCluster,
})
const serviceResponse = await escClient.send(describeServiceCommand)  

const desiredCount = serviceResponse.services[0].desiredCount

cosnt scalingUpDesiredCount = desiredCount + ${scalingSize}
cosnt scalingDownDesiredCount = desiredCount + ${scalingSize}

const updateParams = {
     service: ecsService,
     cluster: ecsCluster,
     desiredCount: scalingUpDesiredCount // or scalingDownDesiredCount,
}

const updateServiceCommand = new UpdateServiceCommand(updateParams)

await escClient.send(updateServiceCommand)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;By setting up an extra target group for Lambda and adjusting traffic weights, we can keep things running smoothly until Fargate catches up. However, there remains a possibility of encountering 503 errors if our CloudWatch metrics fail to detect the traffic surge promptly.&lt;/p&gt;

&lt;p&gt;In the next article, we will investigate how to use Lambda as a fallback for ECS Fargate&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>webdev</category>
      <category>serverless</category>
    </item>
    <item>
      <title>AWS Fargate and SQS for Long-Running Jobs with TypeScript</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Wed, 12 Mar 2025 22:02:26 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-fargate-and-sqs-for-long-running-jobs-with-typescript-2lb4</link>
      <guid>https://dev.to/aws-builders/aws-fargate-and-sqs-for-long-running-jobs-with-typescript-2lb4</guid>
      <description>&lt;p&gt;This article will explain my experience in setting up the AWS ECS Fargate container which handles messages from SQS. The reason for having Fargate handle messages instead of lambda is that the message represents a long-running job which is usually longer than 15 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up ECS Fargate infrastructure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Components Creation:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cluster:&lt;/strong&gt; A Fargate cluster is initialized to house the message processing tasks.&lt;br&gt;
&lt;strong&gt;Task Definition:&lt;/strong&gt; This definition specifies the computing resources required and points to the Dockerfile that contains the application code.&lt;br&gt;
&lt;strong&gt;Fargate Service:&lt;/strong&gt; Represents the service running on Fargate, facilitating public access and defining service scalability.&lt;br&gt;
&lt;strong&gt;SQS Queue:&lt;/strong&gt; An SQS queue is set up to hold the messages to be processed.&lt;/p&gt;

&lt;p&gt;I. Import all needed libraries&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ContainerImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FargateService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FargateTaskDefinition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;LogDrivers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PropagatedTagSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-ecs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;sqs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-sqs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;AdjustmentType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;MetricAggregationType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ScalableTarget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;StepScalingAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-applicationautoscaling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Alarm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ComparisonOperator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MathExpression&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-cloudwatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApplicationScalingAction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-cloudwatch-actions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;II. Create cluster&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create fargate cluster&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SQS_PROCESSING_CLUSTER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// vpc: define your VPC here if needed,&lt;/span&gt;
    &lt;span class="na"&gt;clusterName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SQS_PROCESSING_CLUSTER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;III. Create task definition&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Create a Task Definition for the container to start&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taskDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FargateTaskDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SQS_PROCESSING_CONTAINER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;memoryLimitMiB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;taskDefinition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SQS_PROCESSING_CONTAINER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Path to docker file in your local repository&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContainerImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./src/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dockerfile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// put your environment&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogDrivers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;awsLogs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;streamPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/fargate/sqs-processing/log-group&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;logRetention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RetentionDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;THREE_DAYS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IV. Create a Fargate service&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Create a load-balanced Fargate service and make it public&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FargateService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sqs-processing-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Cluster from step 1&lt;/span&gt;
    &lt;span class="na"&gt;taskDefinition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taskDefinition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Task definition from step 2 &lt;/span&gt;
    &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sqs-processing-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;assignPublicIp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minHealthyPercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxHealthyPercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;propagateTags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PropagatedTagSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SERVICE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;enableExecuteCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;V. Create SQS&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sqs-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;queueName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sqs-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;visibilityTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VI. Allow Fargate to consume SQS message&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantConsumeMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskDefinition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskRole&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Fargate autoscaling
&lt;/h2&gt;

&lt;p&gt;To optimize resource utilization and costs, the setup includes autoscaling configurations. These ensure that the Fargate tasks scale up when messages are pending in the SQS queue and scale down once all messages have been processed or when no messages are present.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaling up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scalingTarget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;autoScaleTaskCount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;maxCapacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minCapacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Scale up when SQS has visible messages&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numberOfMessagesVisibleMetric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metricApproximateNumberOfMessagesVisible&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Scale up if queue has messages&lt;/span&gt;
  &lt;span class="nx"&gt;scalingTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scaleOnMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;QueueMessagesVisibleScaling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;numberOfMessagesVisibleMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;evaluationPeriods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scalingSteps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// DO not tear down task if there is no messages visible in SQS&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;cooldown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scale down
&lt;/h3&gt;

&lt;p&gt;Combine 3 metrics to avoid scaling down during message processing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messagesSentMetric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sqsQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metricNumberOfMessagesSent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messagesVisibleMetric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sqsQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metricApproximateNumberOfMessagesVisible&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messagesNotVisibleMetric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sqsQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metricApproximateNumberOfMessagesNotVisible&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Combine all metrics to create a custom metric using a math expression&lt;/span&gt;
  &lt;span class="c1"&gt;// The expression adds the 3 metrics, and we expect the sum to be zero to scale down.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customMetric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MathExpression&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messagesSent + messagesVisible + messagesNotVisible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;usingMetrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;messagesSent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messagesSentMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;messagesVisible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messagesVisibleMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;messagesNotVisible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messagesNotVisibleMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create alarm and scalable target
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
  &lt;span class="c1"&gt;// Add an alarm to our Fargate service CPU usage&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scaleInInit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Alarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ScaleDownAlarm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;alarmDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;When there is no messages in SQS, scale down to 0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;evaluationPeriods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;datapointsToAlarm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;comparisonOperator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComparisonOperator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LESS_THAN_OR_EQUAL_TO_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Define our auto scaling target for our Fargate service&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scalableTarget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ScalableTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromScalableTargetId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ZeroSQSMessage-ScalableTarget&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`service/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clusterName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|ecs:service:DesiredCount|ecs`&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create step scaling action and alarm action
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
  &lt;span class="c1"&gt;// Define the action taken on our scaling target&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scalingAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StepScalingAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scaleToZero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;scalingTarget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;scalableTarget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;adjustmentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AdjustmentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXACT_CAPACITY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;metricAggregationType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MetricAggregationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAXIMUM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cooldown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Create the adjustment made by our action&lt;/span&gt;
  &lt;span class="nx"&gt;scalingAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addAdjustment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;adjustment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;upperBound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Finally add our alarm action to our Fargate alarm&lt;/span&gt;
  &lt;span class="nx"&gt;scaleInInit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addAlarmAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApplicationScalingAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scalingAction&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Bussines code implementation
&lt;/h2&gt;

&lt;p&gt;Now that our infra is set, it is time to write business code to process messages&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker file setup
&lt;/h3&gt;

&lt;p&gt;The Dockerfile is carefully crafted to prepare the Node environment, execute the necessary installations, and set the application to run indefinitely, constantly listening for SQS messages to process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM --platform=linux/amd64 node:18 AS appbuild
WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY ./ /app/

RUN npm run build

FROM node:18

WORKDIR /app
COPY --from=appbuild /app/ .
RUN npm prune production
ENV NODE_ENV=production


CMD ["node",  "/app/fargate/index.js" ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Main business index.ts
&lt;/h3&gt;

&lt;p&gt;A TypeScript file (index.ts) defines the core logic for message reception, processing, and deletion. This involves interacting with the SQS service through AWS SDK commands, specifically focusing on receiving and deleting messages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/client-sqs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// here we can also add delay&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processMessages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processMessages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReceiveMessageCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;QueueUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sqs-url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;WaitTimeSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// time to consume messages&lt;/span&gt;
      &lt;span class="na"&gt;MaxNumberOfMessages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sqsClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Messages&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No messages received!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Received messages:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No body in message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// here you need to handle you SQS message&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error processing message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="c1"&gt;// Optionally you can send message to Dead-letter SQS&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Now message is processed, we need to delete it&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deleteCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DeleteMessageCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;QueueUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sqs-url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;ReceiptHandle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReceiptHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sqsClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deleteCommand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Uncaught error&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error processing messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This article outlines how to set up an AWS ECS Fargate container to process long-running jobs using messages from an SQS queue, an approach especially useful when tasks exceed the 15-minute execution limit of AWS Lambda. It covers the initial steps of setting up the ECS Fargate infrastructure, including creating a Fargate cluster, task definition, service, and SQS queue, followed by enabling autoscaling based on the queue’s workload. Additionally, it describes implementing business logic within a Docker container to continuously process and manage SQS messages. This setup leverages the scalability and flexibility of AWS services to efficiently handle long-running tasks while optimizing resource usage and costs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Additional Considerations
&lt;/h2&gt;

&lt;p&gt;When implementing this solution, consider monitoring and alerting mechanisms to keep track of the system’s health and performance. Employing AWS CloudWatch can provide insights into task execution metrics and trigger alarms in case of anomalies. Additionally, exploring the integration of dead-letter queues (DLQs) could further bolster the system’s resilience by handling messages that cannot be processed successfully.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>containers</category>
      <category>awscommunity</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Mastering CQRS: Command Query Responsibility Segregation in Modern Applications - Software Architecture Patterns</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Fri, 21 Feb 2025 15:07:27 +0000</pubDate>
      <link>https://dev.to/ohanhaliuk/mastering-cqrs-command-query-responsibility-segregation-in-modern-applications-software-4iko</link>
      <guid>https://dev.to/ohanhaliuk/mastering-cqrs-command-query-responsibility-segregation-in-modern-applications-software-4iko</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Modern applications demand scalability, performance, and flexibility. One common challenge in both monolithic and microservices architectures is handling reads and writes efficiently.&lt;/p&gt;

&lt;p&gt;CQRS (Command Query Responsibility Segregation) is a software design pattern that separates read and write operations to optimize performance, scalability, and security.&lt;/p&gt;

&lt;p&gt;In this article, we explore how CQRS works, its real-world microservice applications, and how it can be implemented on cloud platforms like AWS.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Why CQRS? The Problem with Traditional CRUD
&lt;/h2&gt;

&lt;p&gt;In traditional CRUD-based architectures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same database schema is used for both reading and writing.&lt;/li&gt;
&lt;li&gt;Complex joins and transactions slow down queries.&lt;/li&gt;
&lt;li&gt;High-volume reads impact write performance, making scaling difficult.&lt;/li&gt;
&lt;li&gt;Security concerns arise when sensitive data is exposed to read operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 &lt;strong&gt;CQRS solves these issues by splitting reads and writes into separate models!&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Understanding CQRS
&lt;/h2&gt;

&lt;p&gt;CQRS divides the system into two distinct models:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Command Model (Write Operations)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Handles data modifications: Create, Update, Delete.&lt;/li&gt;
&lt;li&gt;Uses a normalized schema optimized for transactions.&lt;/li&gt;
&lt;li&gt;Ensures strong consistency.&lt;/li&gt;
&lt;li&gt;Commands don't return data, only success/failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Query Model (Read Operations)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Handles data retrieval: Get, List, Search.&lt;/li&gt;
&lt;li&gt;Uses denormalized views optimized for fast reads.&lt;/li&gt;
&lt;li&gt;Can be cached and scaled independently.&lt;/li&gt;
&lt;li&gt;No side effects, ensuring read efficiency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By separating writes (commands) from reads (queries), CQRS enables performance and security improvements.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. How CQRS Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;User sends a command (e.g., Create Order).&lt;/li&gt;
&lt;li&gt;Command service updates the database.&lt;/li&gt;
&lt;li&gt;An event is published (e.g., &lt;code&gt;OrderCreated&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A separate read model is updated asynchronously.&lt;/li&gt;
&lt;li&gt;User queries the read model for updated data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;💡 &lt;strong&gt;Commands modify data, queries fetch data – both optimized separately!&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. CQRS with Event Sourcing
&lt;/h2&gt;

&lt;p&gt;CQRS often works with &lt;strong&gt;Event Sourcing&lt;/strong&gt;, where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of updating a row, each change is stored as an immutable event.&lt;/li&gt;
&lt;li&gt;Events rebuild the state in real-time.&lt;/li&gt;
&lt;li&gt;Provides auditability and rollback support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📌 &lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;UserCreated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;UserUpdated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;System state is reconstructed by replaying events!&lt;/p&gt;




&lt;h2&gt;
  
  
  5. CQRS on AWS
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjty1xcpioyaz727ta6w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjty1xcpioyaz727ta6w.png" alt="AWS CQRS" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CQRS can be implemented using AWS services:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Write Model (Commands):&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;API Gateway + Lambda (or EC2/ECS)&lt;/li&gt;
&lt;li&gt;Amazon RDS&lt;/li&gt;
&lt;li&gt;EventBridge / SNS / SQS for event-driven processing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Read Model (Queries):&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DynamoDB / ElastiCache / OpenSearch&lt;/li&gt;
&lt;li&gt;API Gateway + Lambda for fast retrieval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚀 &lt;strong&gt;Example CQRS Implementation on AWS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Command → API Gateway → Lambda → RDS → EventBridge
Query   → API Gateway → Lambda → DynamoDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. CQRS with Materialized Views and a Single Database
&lt;/h2&gt;

&lt;p&gt;CQRS does not always require separate databases. A single database can still benefit from CQRS using &lt;strong&gt;Materialized Views&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2b7e2pr135qv26414ewo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2b7e2pr135qv26414ewo.png" alt="CQRS Materialized Views" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Commands (writes) modify normalized tables in the database.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Materialized View (MV)&lt;/strong&gt; is used to store precomputed read models.&lt;/li&gt;
&lt;li&gt;Queries (reads) access the Materialized View instead of complex joins.&lt;/li&gt;
&lt;li&gt;The Materialized View updates periodically or via triggers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;📌 &lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write Table:&lt;/strong&gt; Orders Table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Materialized View:&lt;/strong&gt; Orders Read Model&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. CQRS vs. Traditional CRUD
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Traditional CRUD&lt;/th&gt;
&lt;th&gt;CQRS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single Schema&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance Issues&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;❌ Low&lt;/td&gt;
&lt;td&gt;✅ High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complexity&lt;/td&gt;
&lt;td&gt;✅ Low&lt;/td&gt;
&lt;td&gt;❌ Higher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event-Driven&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;💡 &lt;strong&gt;CQRS trades complexity for scalability and performance!&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The CQRS pattern is an essential tool for high-scale applications, ensuring efficient reads and writes while enabling performance, security, and flexibility.&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;Have you implemented CQRS in production? Share your thoughts below!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🔔 &lt;strong&gt;Follow for more insights on software architecture and cloud computing!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>programming</category>
      <category>webdev</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Ensuring Data Consistency with the Transactional Outbox Pattern on AWS</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Fri, 14 Feb 2025 21:53:02 +0000</pubDate>
      <link>https://dev.to/ohanhaliuk/ensuring-data-consistency-with-the-transactional-outbox-pattern-on-aws-27fi</link>
      <guid>https://dev.to/ohanhaliuk/ensuring-data-consistency-with-the-transactional-outbox-pattern-on-aws-27fi</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In distributed systems, ensuring &lt;strong&gt;data consistency&lt;/strong&gt; across services is crucial. When &lt;strong&gt;database transactions and message queues&lt;/strong&gt; are not atomic, failures can cause inconsistencies.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Transactional Outbox Pattern&lt;/strong&gt; ensures &lt;strong&gt;database writes and message publishing occur atomically&lt;/strong&gt;, avoiding lost events.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Problem: Database and Messaging Are Not Atomic
&lt;/h2&gt;

&lt;p&gt;Imagine an Order Service that:&lt;br&gt;
Writes order data to Amazon RDS (PostgreSQL/MySQL).&lt;br&gt;
Publishes an event (OrderPlaced) to Amazon SQS for other services (e.g., Inventory, Shipping).&lt;/p&gt;

&lt;p&gt;🔥 The Failure Scenario:&lt;br&gt;
The service writes an order to RDS ✅&lt;br&gt;
The service fails to publish OrderPlaced to SQS ❌ (network error, timeout, etc.)&lt;/p&gt;

&lt;p&gt;💥 Problem: The order is stored in the database, but other services (Inventory, Shipping) never receive the event.&lt;br&gt;
🚨 Data inconsistency can lead to:&lt;br&gt;
Customers being charged without order fulfillment.&lt;br&gt;
No inventory updates, leading to overselling.&lt;br&gt;
No shipping triggered, leading to support escalations.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Transactional Outbox Pattern Solution
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgl0kvzew9gt6y0w8tpc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgl0kvzew9gt6y0w8tpc.png" alt="Transactional Outbox AWS" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🔹 &lt;strong&gt;Step 1: Define Outbox Table&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_outbox&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;processed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔹 &lt;strong&gt;Step 2: Write Order &amp;amp; Event in One Transaction (TypeScript)&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-db-connection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BEGIN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO orders (order_id, customer_id, total_price) VALUES ($1, $2, $3)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalPrice&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO order_outbox (order_id, event_type, payload) VALUES ($1, $2, $3)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OrderPlaced&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalPrice&lt;/span&gt; &lt;span class="p"&gt;})]);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMMIT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ROLLBACK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔹 &lt;strong&gt;Step 3: Poll &amp;amp; Publish Events (AWS Lambda / ECS Task)&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SNS&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-db-connection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SNS&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topicArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arn:aws:sns:us-east-1:your-account-id:order-events&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processOutbox&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT id, payload FROM order_outbox WHERE processed_at IS NULL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;TopicArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;topicArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE order_outbox SET processed_at = NOW() WHERE id = $1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to send event &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error processing outbox:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Why We Can't Just Rollback Transaction with SNS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔹 SNS and Databases Don't Share Transactions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Databases (RDS, PostgreSQL, MySQL) support &lt;strong&gt;ACID transactions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Amazon SNS is an external service and cannot be part of the same transaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔹 Failure Scenarios Without an Outbox
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DB Insert Succeeds, SNS Fails:

&lt;ul&gt;
&lt;li&gt;Order is stored in &lt;strong&gt;RDS&lt;/strong&gt; ✅&lt;/li&gt;
&lt;li&gt;SNS message fails ❌&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Other services never receive the event!&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;SNS Message Sent, DB Insert Fails:

&lt;ul&gt;
&lt;li&gt;SNS message sent ✅&lt;/li&gt;
&lt;li&gt;DB insert fails ❌&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Other services see an event for an order that does not exist!&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔹 What Should Happen in Case of Failure?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;If SNS fails:&lt;/strong&gt; The event remains in the outbox and can be retried later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If DB transaction fails:&lt;/strong&gt; Nothing is committed, ensuring consistency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries &amp;amp; Dead-letter Queue (DLQ):&lt;/strong&gt; Failed SNS messages can be moved to a DLQ for further debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Real-World Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;E-commerce Order Processing:&lt;/strong&gt; Ensuring orders are reliably propagated to inventory, billing, and shipping services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment Processing:&lt;/strong&gt; Making sure transactions trigger correct notifications and reports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Signups:&lt;/strong&gt; Ensuring emails or notifications are reliably sent after new user registration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IoT Device Events:&lt;/strong&gt; Ensuring telemetry data from devices is consistently published.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Difference from Saga Pattern
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Transactional Outbox&lt;/th&gt;
&lt;th&gt;Saga Pattern&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Approach&lt;/td&gt;
&lt;td&gt;Ensures event publishing is atomic&lt;/td&gt;
&lt;td&gt;Splits transactions into smaller steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use Case&lt;/td&gt;
&lt;td&gt;Ensures reliable messaging&lt;/td&gt;
&lt;td&gt;Handles multi-step business processes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complexity&lt;/td&gt;
&lt;td&gt;Simpler&lt;/td&gt;
&lt;td&gt;More complex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Example&lt;/td&gt;
&lt;td&gt;Reliable event publishing to SNS&lt;/td&gt;
&lt;td&gt;Hotel &amp;amp; flight booking with rollback steps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Transactional Outbox Pattern&lt;/strong&gt; ensures &lt;strong&gt;event consistency&lt;/strong&gt; by making database transactions and event publishing atomic. Implementing it with &lt;strong&gt;Amazon RDS, SNS, and Lambda/ECS&lt;/strong&gt; provides a &lt;strong&gt;reliable, scalable, and decoupled&lt;/strong&gt; solution.&lt;/p&gt;

&lt;p&gt;🚀 Have you implemented this pattern? Share your experience in the comments!&lt;br&gt;&lt;br&gt;
🔔 &lt;strong&gt;Follow for more AWS architecture insights!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>microservices</category>
      <category>database</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Serverless MapReduce for Excel: Scale Your Marketing Data with AWS</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Sat, 08 Feb 2025 18:27:08 +0000</pubDate>
      <link>https://dev.to/ohanhaliuk/serverless-mapreduce-for-excel-scale-your-marketing-data-with-aws-2460</link>
      <guid>https://dev.to/ohanhaliuk/serverless-mapreduce-for-excel-scale-your-marketing-data-with-aws-2460</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;MapReduce is a programming model for processing large datasets in parallel. It splits the input data into chunks (&lt;strong&gt;Map&lt;/strong&gt;), then combines or aggregates results (&lt;strong&gt;Reduce&lt;/strong&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Map:&lt;/strong&gt; Break down data into smaller parts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shuffle/Sort:&lt;/strong&gt; Group related data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce:&lt;/strong&gt; Aggregate or combine into final results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's learn the &lt;strong&gt;Map-Reduce pattern&lt;/strong&gt; with a real-world example: &lt;strong&gt;Event-Driven Serverless “MapReduce” AWS Architecture&lt;/strong&gt; for Excel-Based Marketing Campaign Analytics.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Overview
&lt;/h2&gt;

&lt;p&gt;If you’re dealing with Excel sheets full of marketing metrics (e.g., campaigns, CPC, revenue), this AWS &lt;strong&gt;serverless pipeline&lt;/strong&gt; helps process and aggregate data automatically—&lt;strong&gt;no cluster management needed.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Steps:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Upload Excel:&lt;/strong&gt; A marketing manager uploads a spreadsheet to an &lt;strong&gt;Amazon S3&lt;/strong&gt; bucket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map Lambda:&lt;/strong&gt; Parses each row (&lt;strong&gt;date, campaign, source, cost, etc.&lt;/strong&gt;) and saves intermediate results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce Lambda:&lt;/strong&gt; Aggregates partial data into a final report for analytics or dashboards.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2. Architecture Flow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnc6k66vacbbbqibc6onc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnc6k66vacbbbqibc6onc.png" alt="AWS Architecture" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Excel File Upload:&lt;/strong&gt; The marketing manager or an automated process places the Excel file into an &lt;strong&gt;S3 bucket&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map Lambda:&lt;/strong&gt; Triggered by an S3 event. It reads and parses each row, storing partial outputs in &lt;strong&gt;S3&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce Lambda:&lt;/strong&gt; Triggered by a subsequent event or schedule. Collects all partial results, aggregates them, and writes the final report to &lt;strong&gt;S3 or a database&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Step-by-Step
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5gtxbm3l9k7wy29gebo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5gtxbm3l9k7wy29gebo.png" alt="Sequence diagram" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User uploads an &lt;strong&gt;Excel file&lt;/strong&gt; with marketing data to an &lt;strong&gt;S3 bucket&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map Lambda&lt;/strong&gt; is triggered by an &lt;strong&gt;S3 event&lt;/strong&gt;, processes each row, and stores intermediate data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce Lambda&lt;/strong&gt; aggregates data across different marketing sources into a &lt;strong&gt;final report&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The processed report can be stored in &lt;strong&gt;S3&lt;/strong&gt; or used for &lt;strong&gt;visualization&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  4. Key Benefits
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;Serverless:&lt;/strong&gt; No servers or clusters to maintain.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Cost-Effective:&lt;/strong&gt; Only pay for &lt;strong&gt;Lambda execution and minimal S3 usage&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Automated Data Ingestion:&lt;/strong&gt; Triggers when an Excel file is uploaded.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Decoupled Architecture:&lt;/strong&gt; Easily modify or extend each step.  &lt;/p&gt;
&lt;h2&gt;
  
  
  5. Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;strong&gt;validation/error handling&lt;/strong&gt; in the “Map” phase for missing columns or invalid data.&lt;/li&gt;
&lt;li&gt;Implement &lt;strong&gt;notifications&lt;/strong&gt; (e.g., email or Slack) when final reports are generated.&lt;/li&gt;
&lt;li&gt;Integrate with &lt;strong&gt;dashboard tools&lt;/strong&gt; (e.g., &lt;strong&gt;QuickSight&lt;/strong&gt;) to visualize aggregated marketing metrics.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  6. Example Transformation Flow
&lt;/h2&gt;

&lt;p&gt;Below is a simple example of how a single row from the Excel file is &lt;strong&gt;transformed&lt;/strong&gt; during the &lt;strong&gt;Map step&lt;/strong&gt;, and then combined in the &lt;strong&gt;Reduce step&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  🔹 &lt;strong&gt;Input Excel Row&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3awryor0825pyx9cbqm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3awryor0825pyx9cbqm.png" alt="Input Excel" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  🔹 &lt;strong&gt;Map Output (Intermediate JSON)&lt;/strong&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-01-07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"campaign"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WinterSales"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Google AD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"impressions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19394975&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"clicks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3878995&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8533789&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;46935900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"revenue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;89216885&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;📂 (This might be stored in an S3 path /{date}/campaign/.)&lt;/p&gt;

&lt;p&gt;🔹 Reduce Step&lt;br&gt;
If there are multiple entries for the same date and campaign (e.g., different sources like SEO, Social, etc.), the Reduce Lambda will sum or aggregate values across all partial outputs.&lt;/p&gt;

&lt;p&gt;🔹 Example Final Excel Report&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Date,Campaign,TotlImpressions,TotalClicks,TotalCost,TotalOrders,TotalRevenue
2025-02-07,WinterSale,30000,500,340.0,16,1120.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Here, we combined data from AdWords, SEO, and other sources for WinterSale on 2025-02-07.)&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Why Not Just Use Excel for Data Transformation?
&lt;/h2&gt;

&lt;p&gt;For small, ad-hoc data tasks, Excel works. But a serverless, automated approach is ideal when you:&lt;/p&gt;

&lt;p&gt;🚀 Need consistent transformations across multiple or frequently updated files.&lt;br&gt;
📈 Require scalability (large datasets slow Excel and risk data limits).&lt;br&gt;
📊 Want integration with dashboards, notifications, or further data pipelines.&lt;br&gt;
🔍 Value version control and repeatable, automated processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. What About AWS Glue?
&lt;/h2&gt;

&lt;p&gt;🔹 AWS Glue&lt;br&gt;
A fully managed ETL service, built on Apache Spark.&lt;br&gt;
✅ Great for big data &amp;amp; complex transformations.&lt;br&gt;
✅ Schema discovery &amp;amp; automatic scaling.&lt;br&gt;
❌ More overhead than Lambda-based solutions.&lt;/p&gt;

&lt;p&gt;🔹 Lambda-Based MapReduce&lt;br&gt;
🚀 Lightweight and cost-effective for small-to-medium datasets.&lt;br&gt;
✅ Easier to set up for frequent, structured Excel processing.&lt;br&gt;
✅ You fully control the transformation logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bottom Line&lt;/strong&gt;:&lt;br&gt;
If your datasets are huge or you need advanced ETL features, AWS Glue is better.&lt;br&gt;
For lighter, serverless jobs, a Lambda-based MapReduce approach is simpler and cheaper.&lt;/p&gt;

&lt;p&gt;💡 Would you like a tutorial on deploying this architecture step-by-step? Drop a comment!&lt;br&gt;
🚀 Follow me for more AWS and data engineering content!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>dataengineering</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Ensuring Data Consistency in Microservices with the Saga Pattern (AWS DynamoDB &amp; PostgreSQL)</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Fri, 07 Feb 2025 20:48:51 +0000</pubDate>
      <link>https://dev.to/ohanhaliuk/ensuring-data-consistency-in-microservices-with-the-saga-pattern-aws-dynamodb-postgresql-4dan</link>
      <guid>https://dev.to/ohanhaliuk/ensuring-data-consistency-in-microservices-with-the-saga-pattern-aws-dynamodb-postgresql-4dan</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Saga pattern&lt;/strong&gt; is a microservices architectural approach that coordinates a series of local transactions across different services through a sequence of events or commands. It aims to maintain data consistency without relying on a monolithic, long-running transaction. Instead, each microservice handles its own local database changes and publishes events to trigger subsequent steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Saga Theory
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Avoid Two-Phase Commit (2PC)?
&lt;/h3&gt;

&lt;p&gt;In a &lt;strong&gt;distributed environment&lt;/strong&gt;, using 2PC can become overly complex, especially at scale. The Saga pattern counters this by breaking a single large transaction into a series of &lt;strong&gt;smaller local transactions&lt;/strong&gt;. Each service:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Performs a local transaction.&lt;/li&gt;
&lt;li&gt;Publishes an event (or sends a message).&lt;/li&gt;
&lt;li&gt;If a subsequent step fails, the pattern invokes &lt;strong&gt;compensating transactions&lt;/strong&gt; to roll back prior steps.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Two Main Approaches
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choreography&lt;/strong&gt;: Each service produces and listens to domain events from other services.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration&lt;/strong&gt;: A centralized controller (the &lt;strong&gt;Orchestrator&lt;/strong&gt;) calls each service in turn and manages rollbacks if something fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Either approach aims to ensure data consistency across services without a global lock or a single transaction manager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use the Saga Pattern?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Consistency&lt;/strong&gt;: Keep each service’s database in sync with the overall business process.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: Each microservice can be scaled independently.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fault Tolerance&lt;/strong&gt;: Isolate failure and roll back or compensate where necessary.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loose Coupling&lt;/strong&gt;: Services interact via events or commands, reducing direct dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example Setup
&lt;/h2&gt;

&lt;p&gt;Consider a typical &lt;strong&gt;e-commerce workflow&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Order Reservation Service&lt;/strong&gt; (Service 1)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment Service&lt;/strong&gt; (Service 2)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shipment/Inventory Service&lt;/strong&gt; (Service 3)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each service uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda&lt;/strong&gt; for compute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon SQS&lt;/strong&gt; for inter-service communication.&lt;/li&gt;
&lt;li&gt;Its own database (e.g., &lt;strong&gt;DynamoDB&lt;/strong&gt; or &lt;strong&gt;PostgreSQL&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  DynamoDB vs. PostgreSQL
&lt;/h3&gt;

&lt;p&gt;For illustration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Order Reservation Service&lt;/strong&gt; uses &lt;strong&gt;DynamoDB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment Service&lt;/strong&gt; uses &lt;strong&gt;RDS (PostgreSQL)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shipment/Inventory Service&lt;/strong&gt; also uses &lt;strong&gt;RDS (PostgreSQL)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Flow Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5cafkb131onshp3jgz3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5cafkb131onshp3jgz3.png" alt="Saga Sequence Diagram" width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Customer Request
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The customer triggers an API call (via &lt;strong&gt;API Gateway&lt;/strong&gt;) to place an order.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Order Reservation Service (DynamoDB)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Triggered by&lt;/strong&gt; the API Gateway call (runs on AWS Lambda).
&lt;/li&gt;
&lt;li&gt;Persists the &lt;strong&gt;order record&lt;/strong&gt; to its &lt;strong&gt;DynamoDB&lt;/strong&gt; table.
&lt;/li&gt;
&lt;li&gt;Returns a “reservation pending” response to the customer.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sends an SQS message&lt;/strong&gt; to the Payment Service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Payment Service (PostgreSQL)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Triggered by&lt;/strong&gt; the SQS message.
&lt;/li&gt;
&lt;li&gt;Attempts to &lt;strong&gt;process payment&lt;/strong&gt; (updates a PostgreSQL DB).
&lt;/li&gt;
&lt;li&gt;On success, &lt;strong&gt;sends an SQS message&lt;/strong&gt; to Shipment/Inventory Service.
&lt;/li&gt;
&lt;li&gt;On failure, &lt;strong&gt;sends a rollback request&lt;/strong&gt; to the Order Reservation Service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Shipment/Inventory Service (PostgreSQL)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Triggered by&lt;/strong&gt; the SQS message from Payment.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirms shipping&lt;/strong&gt; and updates inventory (PostgreSQL).
&lt;/li&gt;
&lt;li&gt;On success, notifies Order Reservation Service of completion.
&lt;/li&gt;
&lt;li&gt;On failure, triggers &lt;strong&gt;compensating actions&lt;/strong&gt;: Payment is reversed, Order Reservation is updated to “Canceled.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Handling Transactions and Rollbacks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DynamoDB (Order Reservation)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local Transactions&lt;/strong&gt;: DynamoDB supports &lt;strong&gt;Transaction Write Items&lt;/strong&gt;, allowing an atomic batch of writes/updates/deletes.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback Strategy&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;subsequent transactional write&lt;/strong&gt; (or delete) to revert the item.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional checks&lt;/strong&gt; to ensure you only rollback items in a pending state.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency&lt;/strong&gt; can be managed using item versioning or conditional expressions.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  PostgreSQL (Payment, Shipment/Inventory)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local Transactions&lt;/strong&gt;: PostgreSQL provides standard SQL transactions with &lt;code&gt;BEGIN&lt;/code&gt;, &lt;code&gt;COMMIT&lt;/code&gt;, &lt;code&gt;ROLLBACK&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback Strategy&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;If failure occurs during an open transaction, &lt;strong&gt;ROLLBACK&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;If the transaction has already committed (e.g., after a credit card charge), a &lt;strong&gt;compensating transaction&lt;/strong&gt; (like a refund) might be necessary.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Detailed Scenarios
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. All Services Succeed
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Customer places an order (API Gateway → Lambda).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order Reservation&lt;/strong&gt; writes to DynamoDB.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment&lt;/strong&gt; processes the payment with a PostgreSQL local transaction.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shipment/Inventory&lt;/strong&gt; updates inventory in its PostgreSQL transaction.
&lt;/li&gt;
&lt;li&gt;Order Reservation finalizes the order after receiving a success message.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Failure in Payment Service
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Order Reservation completes successfully and sends a message to Payment.
&lt;/li&gt;
&lt;li&gt;Payment fails (e.g., insufficient funds).
&lt;/li&gt;
&lt;li&gt;Payment either rolls back the open transaction or issues a &lt;strong&gt;compensating refund&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;Payment notifies Order Reservation.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order Reservation&lt;/strong&gt; updates DynamoDB to mark the order as “Canceled.”&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3. Failure in Shipment/Inventory
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Order Reservation and Payment succeed.
&lt;/li&gt;
&lt;li&gt;Shipment/Inventory fails (DB error, out of stock, etc.).
&lt;/li&gt;
&lt;li&gt;Shipment/Inventory rolls back the transaction if still open.
&lt;/li&gt;
&lt;li&gt;Notifies Payment (for refund if payment was already captured) and Order Reservation (to cancel the order).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  4. Other Potential Failures
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts&lt;/strong&gt;: If Shipment/Inventory never responds, Order Reservation can define a timeout strategy.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial Failures&lt;/strong&gt;: Payment might succeed but fail to write to its DB. Idempotency is critical to handle retries.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dead Letter Queues (DLQs)&lt;/strong&gt;: Repeatedly failing messages can land in a DLQ for manual review.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Error Handling and Compensation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local Rollbacks&lt;/strong&gt;: Each service can undo its changes if notified of an upstream failure.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt;: Use a follow-up &lt;strong&gt;transactional write or delete&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt;: Use &lt;strong&gt;ROLLBACK&lt;/strong&gt; or a compensating &lt;strong&gt;UPDATE/DELETE&lt;/strong&gt; if already committed.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration Logic&lt;/strong&gt;: In the Orchestration approach, the orchestrator (e.g., the Order Reservation service) can track the overall saga state, sending compensating commands when needed.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choreography&lt;/strong&gt;: Each service publishes domain events for the others to consume.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency&lt;/strong&gt;: Ensure repeated messages or API calls don’t create duplicate transactions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring &amp;amp; Alerting&lt;/strong&gt;: Use Amazon CloudWatch metrics (errors, queue size, latencies).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Employ least-privilege &lt;strong&gt;IAM roles&lt;/strong&gt; for each microservice.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated Tests&lt;/strong&gt;: Integration, partial failure, and timeout tests are crucial for confidence.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB Transactions&lt;/strong&gt;: Use &lt;strong&gt;Transaction Write&lt;/strong&gt; for atomic updates when you need them.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL ACID&lt;/strong&gt;: Leverage strong consistency within each service boundary.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Saga pattern&lt;/strong&gt; is a powerful strategy for orchestrating and maintaining &lt;strong&gt;data consistency&lt;/strong&gt; in distributed microservices. By structuring each step as a local transaction and employing &lt;strong&gt;events&lt;/strong&gt; or an &lt;strong&gt;orchestrator&lt;/strong&gt;, you avoid the pitfalls of large, complex global transactions. Whether you use &lt;strong&gt;DynamoDB transactions&lt;/strong&gt; or &lt;strong&gt;PostgreSQL’s ACID&lt;/strong&gt; properties, the key lies in designing robust &lt;strong&gt;compensating mechanisms&lt;/strong&gt; for failures. With careful planning — including proper &lt;strong&gt;rollbacks&lt;/strong&gt;, &lt;strong&gt;timeouts&lt;/strong&gt;, and &lt;strong&gt;idempotent messages&lt;/strong&gt; — the Saga pattern can reliably handle complex workflows such as e-commerce order processing.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Thanks for reading!&lt;/strong&gt; If you enjoyed this guide, feel free to drop a comment or share how you implement Sagas in your own applications.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>dynamodb</category>
    </item>
    <item>
      <title>Single Responsibility Principle in Javascript</title>
      <dc:creator>Oleksandr Hanhaliuk</dc:creator>
      <pubDate>Fri, 06 Dec 2024 16:31:03 +0000</pubDate>
      <link>https://dev.to/ohanhaliuk/single-responsibility-principle-in-javascript-58gc</link>
      <guid>https://dev.to/ohanhaliuk/single-responsibility-principle-in-javascript-58gc</guid>
      <description>&lt;p&gt;Understanding the Single Responsibility Principle in JavaScript&lt;br&gt;
When writing clean, maintainable code, one of the most important principles to follow is the Single Responsibility Principle (SRP). It’s one of the five SOLID principles in software development and ensures that your code is easier to read, test, and modify.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is the Single Responsibility Principle?
&lt;/h2&gt;

&lt;p&gt;The Single Responsibility Principle according to Robert C.Martin states that:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A class or function should have one and only one reason to change.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In simpler terms, each unit of your code (be it a function, class, or module) should be responsible for doing one thing and doing it well. When responsibilities are separated, changes in one area of your code won't unexpectedly affect others, reducing the risk of bugs and making your application easier to maintain and test.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why is SRP Important?
&lt;/h2&gt;

&lt;p&gt;Without SRP, you might face problems like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tangled Dependencies:&lt;/strong&gt; When a function or class has multiple responsibilities, making a change to one can inadvertently break another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Testability:&lt;/strong&gt; Testing becomes harder when a unit of code does too much, as you’d need to mock unrelated dependencies.
Poor Readability: Large, multi-purpose functions or classes are harder to understand, especially for new developers joining the project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Difficult Maintenance:&lt;/strong&gt; The more responsibilities a unit has, the more effort it takes to isolate and fix bugs or add new features.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Applying SRP in JavaScript
&lt;/h2&gt;

&lt;p&gt;Let’s look at some practical examples of applying SRP in JavaScript.&lt;/p&gt;
&lt;h3&gt;
  
  
  Example 1: Refactoring a Function
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Without SRP&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleUserLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Validate user data&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid user data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Authenticate user&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authentication failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authentication failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Log success&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User logged in successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function does too much: validation, authentication, and logging. Each of these is a distinct responsibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With SRP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can refactor it by breaking it into smaller, single-purpose functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid user data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authenticateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Assume authenticate is defined elsewhere&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authentication failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleUserLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;validateUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;authenticateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User logged in successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, each function has a single responsibility, making it easier to test and modify.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: Refactoring a Class
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Without SRP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A class managing multiple concerns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Save user to DB&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Send email&lt;/span&gt;
        &lt;span class="nx"&gt;emailService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Welcome, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome email sent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, UserManager handles user creation, logging, and sending emails—too many responsibilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With SRP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Refactor by delegating responsibilities to other classes or modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;emailService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Welcome, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notificationService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notificationService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;notificationService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User created and welcome email sent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each class now focuses on a single concern: persistence, notification, or logging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for Following SRP
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Keep Functions Short:&lt;/strong&gt; Aim for functions that are 5–20 lines long and serve one purpose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Descriptive Names:&lt;/strong&gt; A good function or class name reflects its responsibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactor Often:&lt;/strong&gt; If a function feels too big or hard to test, split it into smaller functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group Related Logic:&lt;/strong&gt; Use modules or classes to group related responsibilities, but avoid mixing unrelated ones.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The Single Responsibility Principle is a cornerstone of clean code. By ensuring that every function, class, or module has only one reason to change, you make your JavaScript code more modular, easier to test, and simpler to maintain.&lt;/p&gt;

&lt;p&gt;Start small—pick one messy function or class in your current project and refactor it using SRP. Over time, these small changes will lead to a significant improvement in your codebase.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>webdev</category>
      <category>cleancode</category>
    </item>
  </channel>
</rss>
