<?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: Firdaws Aboulaye</title>
    <description>The latest articles on DEV Community by Firdaws Aboulaye (@faboulaye).</description>
    <link>https://dev.to/faboulaye</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%2F1238334%2F0f89fec4-fb89-4d4d-a56d-bc1a11f8c9e2.jpeg</url>
      <title>DEV Community: Firdaws Aboulaye</title>
      <link>https://dev.to/faboulaye</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/faboulaye"/>
    <language>en</language>
    <item>
      <title>Building a Serverless i18n API with Amazon Nova Lite (Bedrock): Why Cheap Tokens Matter (A Lot)</title>
      <dc:creator>Firdaws Aboulaye</dc:creator>
      <pubDate>Tue, 27 Jan 2026 07:12:52 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-a-serverless-i18n-api-with-aws-nova-lite-bedrock-why-cheap-tokens-matter-a-lot-1lp6</link>
      <guid>https://dev.to/aws-builders/building-a-serverless-i18n-api-with-aws-nova-lite-bedrock-why-cheap-tokens-matter-a-lot-1lp6</guid>
      <description>&lt;p&gt;Translating 10,000 UI message entries into 5 languages (50,000 translations) costs &lt;strong&gt;$0.26 with Amazon Nova Lite&lt;/strong&gt; (production-measured) vs &lt;strong&gt;$41 with GPT-4 Turbo&lt;/strong&gt; — a &lt;strong&gt;99.4% cost reduction&lt;/strong&gt; with smart prompt engineering and batching. These numbers are validated from a live deployment.&lt;/p&gt;

&lt;p&gt;In this article, a “message entry” refers to a single i18n key–value pair&lt;br&gt;
(e.g. &lt;code&gt;auth.error.invalid_password=Invalid password&lt;/code&gt;) translated into a target locale&lt;/p&gt;


&lt;h2&gt;
  
  
  The Economics of LLM-Powered Translation
&lt;/h2&gt;

&lt;p&gt;Internationalization (i18n) at scale is a &lt;em&gt;token usage nightmare&lt;/em&gt;. Hundreds or thousands of short messages → thousands of API calls → thousands of tokens wasted in prompt overhead.&lt;/p&gt;

&lt;p&gt;Let’s ground this in real numbers.&lt;/p&gt;
&lt;h3&gt;
  
  
  Assumptions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Average UI message size&lt;/strong&gt;: ~8 tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt overhead&lt;/strong&gt;: ~50 tokens per call (context + instructions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No batching&lt;/strong&gt;: 1 message per API call&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Token Usage
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Per call&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: 50 (prompt) + 8 = &lt;strong&gt;58 tokens&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Output (translated text): ~8 tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total localized messages (50,000)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input tokens&lt;/strong&gt;: 50,000 × 58 = &lt;strong&gt;2.9M&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output tokens&lt;/strong&gt;: 50,000 × 8 = &lt;strong&gt;0.4M&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Cost Comparison (Updated Pricing)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  📌 OpenAI LLM Pricing (approx current API rates)
&lt;/h3&gt;

&lt;p&gt;Popular production-grade models like &lt;strong&gt;GPT-4o / GPT-4 Turbo&lt;/strong&gt; cost &lt;em&gt;significantly more&lt;/em&gt; than lightweight models. Recent public pricing shows:&lt;br&gt;
&lt;em&gt;Pricing based on publicly documented OpenAI API rates (April 2025). Exact pricing may vary by model version and deployment.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPT-4 Turbo&lt;/strong&gt;: ~$10 / 1M input tokens and ~$30 / 1M output tokens (classic API)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT-4o (more capable successor)&lt;/strong&gt;: ~$2.50 / 1M input and ~$10 / 1M output (2025 standard model pricing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Estimated cost for 50,000 translations (no batching):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input $&lt;/th&gt;
&lt;th&gt;Output $&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4 Turbo&lt;/td&gt;
&lt;td&gt;2.9M × $10/1M = &lt;strong&gt;$29&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;0.4M × $30/1M = &lt;strong&gt;$12&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$41&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4o&lt;/td&gt;
&lt;td&gt;2.9M × $2.50/1M = &lt;strong&gt;$7.25&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;0.4M × $10/1M = &lt;strong&gt;$4.00&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$11.25&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3&gt;
  
  
  💸 AWS Bedrock Nova Lite Pricing
&lt;/h3&gt;

&lt;p&gt;AWS Bedrock’s &lt;strong&gt;Nova Lite&lt;/strong&gt; model is extremely cost-efficient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~$0.06 per 1M &lt;strong&gt;input&lt;/strong&gt; tokens&lt;/li&gt;
&lt;li&gt;~$0.24 per 1M &lt;strong&gt;output&lt;/strong&gt; tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Estimated cost (no batching):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: 2.9M × $0.06/M = &lt;strong&gt;$0.17&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Output: 0.4M × $0.24/M = &lt;strong&gt;$0.10&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total: ~$0.27&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  With Batching (50 messages per call)
&lt;/h3&gt;

&lt;p&gt;Batching drastically reduces prompt overhead since you amortize the 50+ tokens context over many messages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nova Lite total ≈ &lt;strong&gt;$0.12&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;GPT-4o total (same batch assumptions): ~&lt;strong&gt;$3–5+&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This represents roughly a **99% reduction&lt;/em&gt;* in token cost vs mainstream API models.*&lt;/p&gt;


&lt;h2&gt;
  
  
  Real Production Metrics (Validated)
&lt;/h2&gt;

&lt;p&gt;We deployed this API to production and ran comprehensive tests to validate these claims. Here are the &lt;strong&gt;actual measured results&lt;/strong&gt; from a live AWS deployment.&lt;/p&gt;
&lt;h3&gt;
  
  
  Test Configuration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Endpoint&lt;/strong&gt;: AWS Lambda (Python 3.13) + API Gateway (Regional)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model&lt;/strong&gt;: &lt;code&gt;global.amazon.nova-2-lite-v1:0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt;: 128 MB memory, 30s timeout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Region&lt;/strong&gt;: eu-central-1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test date&lt;/strong&gt;: January 2026&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Batch Performance (50 messages per request)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input tokens&lt;/td&gt;
&lt;td&gt;821&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output tokens&lt;/td&gt;
&lt;td&gt;860&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per request&lt;/td&gt;
&lt;td&gt;$0.000256&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost per message&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.00000511&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duration&lt;/td&gt;
&lt;td&gt;3.8 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Multi-Language Validation
&lt;/h3&gt;

&lt;p&gt;Tested with identical 50-message batches across multiple languages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Input Tokens&lt;/th&gt;
&lt;th&gt;Output Tokens&lt;/th&gt;
&lt;th&gt;Cost (USD)&lt;/th&gt;
&lt;th&gt;Duration&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;French&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;821&lt;/td&gt;
&lt;td&gt;860&lt;/td&gt;
&lt;td&gt;$0.000256&lt;/td&gt;
&lt;td&gt;3.8s&lt;/td&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spanish&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;821&lt;/td&gt;
&lt;td&gt;854&lt;/td&gt;
&lt;td&gt;$0.000254&lt;/td&gt;
&lt;td&gt;3.6s&lt;/td&gt;
&lt;td&gt;Slightly faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Japanese&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;821&lt;/td&gt;
&lt;td&gt;843&lt;/td&gt;
&lt;td&gt;$0.000252&lt;/td&gt;
&lt;td&gt;5.1s&lt;/td&gt;
&lt;td&gt;+36% slower (CJK)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key observations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input tokens remain constant (same English source)&lt;/li&gt;
&lt;li&gt;Output tokens vary by ~2% based on target language verbosity&lt;/li&gt;
&lt;li&gt;Japanese takes 36% longer due to character encoding complexity&lt;/li&gt;
&lt;li&gt;Cost variance between languages: negligible (&amp;lt;2%)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Translation Quality Examples
&lt;/h3&gt;

&lt;p&gt;Real translations from production:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication flow&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;EN: "Sign In"
FR: "Se connecter"
ES: "Iniciar sesión"
JA: "サインイン"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Error message&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;EN: "An error occurred. Please try again."
FR: "Une erreur s'est produite. Veuillez réessayer."
ES: "Se produjo un error. Inténtalo de nuevo."
JA: "エラーが発生しました。もう一度お試しください。"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Variable preservation&lt;/strong&gt; (100% success rate):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EN: "Welcome {{username}}!"
FR: "Bienvenue {{username}} !"
ES: "¡Bienvenido {{username}}!"
JA: "ようこそ {{username}}！"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cost Projections from Real Data
&lt;/h3&gt;

&lt;p&gt;Based on measured cost per message ($0.00000511):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Batches&lt;/th&gt;
&lt;th&gt;Total Cost&lt;/th&gt;
&lt;th&gt;Time Estimate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;50 messages&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;$0.00026&lt;/td&gt;
&lt;td&gt;3.8 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000 messages&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;$0.0051&lt;/td&gt;
&lt;td&gt;~1 minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000 messages&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;$0.0511&lt;/td&gt;
&lt;td&gt;~13 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;10,000 × 5 languages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.26&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~65 minutes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Actual vs Theoretical Comparison
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: 10,000 messages translated into 5 languages (50,000 total translations)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Theoretical&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Measured&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Variance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4 Turbo&lt;/td&gt;
&lt;td&gt;$41.00&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4o&lt;/td&gt;
&lt;td&gt;$11.25&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nova Lite&lt;/td&gt;
&lt;td&gt;$0.27 (no batch) / $0.12 (batched)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.26&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Within range&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Savings vs GPT-4 Turbo&lt;/strong&gt;: $41.00 - $0.26 = &lt;strong&gt;$40.74 (99.4% reduction)&lt;/strong&gt; ✅&lt;/p&gt;

&lt;h3&gt;
  
  
  Total Experiment Cost
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total test requests&lt;/strong&gt;: 14 (across all batch sizes and languages)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total measured cost&lt;/strong&gt;: $0.00223&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highest single request cost&lt;/strong&gt;: $0.00026 (50 messages)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These real-world results validate the theoretical calculations and demonstrate that Nova Lite delivers &lt;strong&gt;production-ready, cost-effective translations at scale&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why i18n Translation Explodes Token Costs
&lt;/h2&gt;

&lt;p&gt;Internationalization is uniquely expensive because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Volume&lt;/strong&gt;: Thousands of UI messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repetition&lt;/strong&gt;: Same short prompt structure over and over&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple locales&lt;/strong&gt;: EN → FR, ES, DE, IT, PT, JA…&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated pipelines&lt;/strong&gt;: CI/CD often regenerates translations on every release&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Models help with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Idiomatic translation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Variable preservation (&lt;code&gt;{{username}}&lt;/code&gt;)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tone consistency&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But traditional LLM pricing makes these workflows costly unless you optimize.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  Amazon Nova Lite + Smart Prompt Engineering
&lt;/h3&gt;

&lt;p&gt;Enter &lt;strong&gt;Amazon Nova Lite&lt;/strong&gt;, Amazon's ultra-efficient foundation model — basically the &lt;strong&gt;Toyota Corolla of LLMs&lt;/strong&gt;:&lt;br&gt;
cheap, reliable, and it gets the job done.&lt;/p&gt;

&lt;p&gt;It’s not trying to write poetry or marketing slogans. It’s built for high-volume, low-variance workloads like translation, classification, and structured transformation — exactly what i18n needs.&lt;/p&gt;

&lt;p&gt;Amazon Nova Lite isn’t glamorous, but it’s &lt;strong&gt;cheap and capable&lt;/strong&gt; — ideal for straightforward translation tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nova Lite Token Costs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: ~$0.06/1M&lt;/li&gt;
&lt;li&gt;Output: ~$0.24/1M&lt;/li&gt;
&lt;li&gt;Affordable enough to handle i18n at scale without breaking the budget.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  3 Token Optimization Patterns
&lt;/h2&gt;
&lt;h3&gt;
  
  
  🔹 Pattern 1 — Prompt Minimalism
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Bad (verbose):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a professional translator...
Text to translate: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome to {{app_name}}!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
Please provide the translation now.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good (tight + structured):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Translate &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; to&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. JSON only.
Rules: preserve {{{{variables}}}}; match tone.
Input:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Output format: [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;translated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;➡️ &lt;em&gt;Smaller prompts = fewer tokens = lower costs.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🔹 Pattern 2 — Batch Processing
&lt;/h3&gt;

&lt;p&gt;Sending one message per request kills throughput and adds 50+ overhead tokens every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad: 50 calls&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;translate_single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good: 1 batch call&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;translate_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Payoff:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fewer tokens in repetitive instructions&lt;/li&gt;
&lt;li&gt;Far less latency&lt;/li&gt;
&lt;li&gt;Lower total cost&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🔹 Pattern 3 — Structured Outputs
&lt;/h3&gt;

&lt;p&gt;Free-form text wastes tokens and costs on parsing overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured JSON:&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="p"&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;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"greeting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"translated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Bonjour"&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="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 removes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excess text&lt;/li&gt;
&lt;li&gt;Human-readable labels&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Parsing overhead&lt;/p&gt;

&lt;p&gt;→ for predictable token usage&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Production-Ready Implementation Patterns
&lt;/h2&gt;

&lt;p&gt;Your i18n API should incorporate these practical engineering patterns:&lt;/p&gt;

&lt;h3&gt;
  
  
  🧱 1. Separation of Concerns
&lt;/h3&gt;

&lt;p&gt;Split:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP handler&lt;/li&gt;
&lt;li&gt;Translation service (Bedrock client + prompt templates)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes your service easier to test and evolve.&lt;/p&gt;




&lt;h3&gt;
  
  
  📄 2. External Prompt Templates
&lt;/h3&gt;

&lt;p&gt;Loading prompts from files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enables version control&lt;/li&gt;
&lt;li&gt;No redeploy for prompt tweaks&lt;/li&gt;
&lt;li&gt;Keeps code clean&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🔁 3. Resource Reuse
&lt;/h3&gt;

&lt;p&gt;Initialize Bedrock clients once per Lambda instance (module scope) — reuse on warm starts.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔍 4. Observability &amp;amp; Cost Metrics
&lt;/h3&gt;

&lt;p&gt;Emit CloudWatch metrics for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input tokens&lt;/li&gt;
&lt;li&gt;Output tokens&lt;/li&gt;
&lt;li&gt;Real USD cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Track spend and set alarms!&lt;/p&gt;




&lt;h3&gt;
  
  
  ⚠️ 5. Input Validation
&lt;/h3&gt;

&lt;p&gt;Validate early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Require &lt;code&gt;messages&lt;/code&gt;, &lt;code&gt;locale&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enforce maximum batch size&lt;/p&gt;

&lt;p&gt;→ Fail fast = fewer expensive API calls&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example Request / Response
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Request:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://…/translate &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "messages":[{"key":"hi","content":"Hello"}],
  "locale":"fr"
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"translations"&lt;/span&gt;&lt;span class="p"&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;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"hi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"original"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"translated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Bonjour"&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="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"global.amazon.nova-2-lite-v1:0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tokens_used"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;145&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;67&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_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.0000249&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;234&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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Model Selection Guide
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Best Model&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;High-volume i18n&lt;/td&gt;
&lt;td&gt;Nova Lite&lt;/td&gt;
&lt;td&gt;Ultra-low cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creative marketing&lt;/td&gt;
&lt;td&gt;GPT-4o / GPT-4.1&lt;/td&gt;
&lt;td&gt;Nuance &amp;amp; tone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legal / medical&lt;/td&gt;
&lt;td&gt;High-accuracy large models&lt;/td&gt;
&lt;td&gt;Precision matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time micro latency&lt;/td&gt;
&lt;td&gt;Ultra-small models&lt;/td&gt;
&lt;td&gt;Fastest inference&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;i18n is a token hog.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Amazon Nova Lite offers orders-of-magnitude cost savings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;at ~$0.06/1M input and ~$0.24/1M output — ideal for translation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prompt engineering + batching&lt;/strong&gt; shrinks costs dramatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Structured responses reduce parsing complexity and waste.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cost tracking lets you enforce budgets and monitor usage.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Wrap-Up
&lt;/h2&gt;

&lt;p&gt;Total cost for 50,000 translations (10,000 messages × 5 languages):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Without batching&lt;/th&gt;
&lt;th&gt;With batching (measured)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4 Turbo&lt;/td&gt;
&lt;td&gt;~$41.00&lt;/td&gt;
&lt;td&gt;~$16–20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4o&lt;/td&gt;
&lt;td&gt;~$11.25&lt;/td&gt;
&lt;td&gt;~$3–5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nova Lite&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$0.27&lt;/strong&gt; (theoretical)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$0.26&lt;/strong&gt; ✅ (measured in production)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Validated savings: 99.4% cheaper&lt;/strong&gt; than GPT-4 Turbo ($40.74 saved) when running your i18n pipeline with an optimized Nova Lite + Bedrock approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production-tested&lt;/strong&gt;: These numbers are from real API calls to a live deployment, not estimates.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;All code and templates live in the &lt;a href="https://github.com/faboulaye/i18n-ai" rel="noopener noreferrer"&gt;i18n-ai repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Deploy, watch your token usage, and let your translation costs shrink.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reproduce the Experiments
&lt;/h3&gt;

&lt;p&gt;The production metrics in this article are fully reproducible. The repo includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test datasets&lt;/strong&gt;: &lt;code&gt;/test-data/&lt;/code&gt; directory with various batch sizes and languages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experiment script&lt;/strong&gt;: &lt;code&gt;/scripts/run-experiments.py&lt;/code&gt; - automated test runner&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Results&lt;/strong&gt;: &lt;code&gt;/experiment-results.md&lt;/code&gt; - raw data from our production tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run the experiments yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;task run-experiment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This transparency allows you to validate our claims with your own deployment.&lt;br&gt;
If you replicate these experiments with your own message properties or language sets,&lt;br&gt;
feel free to share your results — I’d love to compare!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>bedrock</category>
      <category>nova</category>
    </item>
    <item>
      <title>What I Learned Using Specification-Driven Development with Kiro</title>
      <dc:creator>Firdaws Aboulaye</dc:creator>
      <pubDate>Mon, 12 Jan 2026 08:24:03 +0000</pubDate>
      <link>https://dev.to/aws-builders/what-i-learned-using-specification-driven-development-with-kiro-pdj</link>
      <guid>https://dev.to/aws-builders/what-i-learned-using-specification-driven-development-with-kiro-pdj</guid>
      <description>&lt;p&gt;For a long time, I thought specifications were just a polite way to slow engineers down.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Big documents.
&lt;/li&gt;
&lt;li&gt;Outdated diagrams.
&lt;/li&gt;
&lt;li&gt;Things nobody reads after sprint one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I actually used specification-driven development with Kiro on a real project, and my opinion changed completely.&lt;/p&gt;

&lt;p&gt;This article is not about a product, a framework, or a startup idea.&lt;br&gt;&lt;br&gt;
It is about what changed in how I think and work as an engineer once I started treating specifications as first-class citizens.&lt;/p&gt;




&lt;h2&gt;
  
  
  I Used to Start with Code (and Regret It Later)
&lt;/h2&gt;

&lt;p&gt;My old workflow looked like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;“I roughly know what needs to be built”&lt;/li&gt;
&lt;li&gt;Sketch an architecture in my head&lt;/li&gt;
&lt;li&gt;Start coding&lt;/li&gt;
&lt;li&gt;Discover edge cases halfway through&lt;/li&gt;
&lt;li&gt;Refactor&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It felt fast.&lt;br&gt;&lt;br&gt;
It was not.&lt;/p&gt;

&lt;p&gt;Most of the time, I was not fixing bugs. I was fixing unclear decisions I never made explicitly.&lt;/p&gt;

&lt;p&gt;Using Kiro forced me to stop before step two.&lt;/p&gt;




&lt;h2&gt;
  
  
  Writing the Requirement First Was Uncomfortable
&lt;/h2&gt;

&lt;p&gt;The first thing I had to write was a requirement, not code.&lt;/p&gt;

&lt;p&gt;Something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As an entity owner, I want to register my organization and receive credentials,&lt;br&gt;&lt;br&gt;
so that I can submit data for processing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Followed by acceptance criteria such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the system accepts only basic information&lt;/li&gt;
&lt;li&gt;credentials are generated once&lt;/li&gt;
&lt;li&gt;a default configuration is created&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No cloud provider.&lt;br&gt;&lt;br&gt;
No database.&lt;br&gt;&lt;br&gt;
No crypto algorithms.&lt;/p&gt;

&lt;p&gt;At first, this felt artificial.&lt;br&gt;&lt;br&gt;
I already knew how I wanted to build it, so why slow down?&lt;/p&gt;

&lt;p&gt;Then something interesting happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Spec Exposed Assumptions I Didn’t Know I Had
&lt;/h2&gt;

&lt;p&gt;As I wrote the acceptance criteria, questions started appearing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What exactly does “registered” mean?&lt;/li&gt;
&lt;li&gt;What exists after registration?&lt;/li&gt;
&lt;li&gt;What must be returned, and what must never be returned again?&lt;/li&gt;
&lt;li&gt;What is intentionally out of scope?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these questions were about code.&lt;/p&gt;

&lt;p&gt;They were about behavior.&lt;/p&gt;

&lt;p&gt;I realized that I usually answered these questions implicitly while coding.&lt;/p&gt;

&lt;p&gt;That is risky.&lt;/p&gt;




&lt;h2&gt;
  
  
  Acceptance Criteria Became My Decision Filter
&lt;/h2&gt;

&lt;p&gt;Once the acceptance criteria were written, they became my favorite tool.&lt;/p&gt;

&lt;p&gt;During implementation, I kept asking myself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this help satisfy an acceptance criterion?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer was no, I stopped.&lt;/p&gt;

&lt;p&gt;This single habit prevented over-engineering, killed nice-to-have features early, and reduced scope creep without meetings.&lt;/p&gt;

&lt;p&gt;Reviews also changed.&lt;br&gt;&lt;br&gt;
Discussions became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which acceptance criterion does this serve?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I feel like this might be useful later.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Kiro Changed That I Didn’t Expect
&lt;/h2&gt;

&lt;p&gt;At this point, a fair question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Couldn’t you do all of this without Kiro?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Technically, yes.&lt;/p&gt;

&lt;p&gt;Practically, I never did.&lt;/p&gt;




&lt;h3&gt;
  
  
  Before Kiro: Acceptance Criteria Were Optional
&lt;/h3&gt;

&lt;p&gt;Before, my workflow looked like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write acceptance criteria&lt;/li&gt;
&lt;li&gt;Design the solution&lt;/li&gt;
&lt;li&gt;Start coding&lt;/li&gt;
&lt;li&gt;Adjust scope along the way&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing stopped me from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding extra fields while I was there&lt;/li&gt;
&lt;li&gt;handling edge cases not in the requirement&lt;/li&gt;
&lt;li&gt;making implementation-driven decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The acceptance criteria existed, but they had no real weight.&lt;/p&gt;




&lt;h3&gt;
  
  
  With Kiro: Acceptance Criteria Became Structural
&lt;/h3&gt;

&lt;p&gt;With Kiro, I could not move forward without:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a defined requirement&lt;/li&gt;
&lt;li&gt;explicit acceptance criteria&lt;/li&gt;
&lt;li&gt;a design derived from them&lt;/li&gt;
&lt;li&gt;tasks mapped back to them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skipping clarity was not forbidden. It was just uncomfortable.&lt;/p&gt;

&lt;p&gt;If I tried to jump ahead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;designs felt premature&lt;/li&gt;
&lt;li&gt;tasks felt vague&lt;/li&gt;
&lt;li&gt;gaps became obvious immediately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kiro did not magically make me write better specs.&lt;br&gt;&lt;br&gt;
It made weak specs impossible to ignore.&lt;/p&gt;




&lt;h3&gt;
  
  
  Less Mental Overhead While Coding
&lt;/h3&gt;

&lt;p&gt;Before Kiro, I constantly asked myself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this enough?&lt;/li&gt;
&lt;li&gt;Am I missing something?&lt;/li&gt;
&lt;li&gt;Should I add this now?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With acceptance criteria embedded into the workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;many decisions were already made&lt;/li&gt;
&lt;li&gt;implementation became mechanical&lt;/li&gt;
&lt;li&gt;confidence increased&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was not about documentation.&lt;/p&gt;

&lt;p&gt;It was about removing uncertainty at the exact moment it hurts the most, while coding.&lt;/p&gt;




&lt;h3&gt;
  
  
  Intent Became a System Constraint
&lt;/h3&gt;

&lt;p&gt;The most important shift was subtle.&lt;/p&gt;

&lt;p&gt;Acceptance criteria stopped being guidance and became constraints that the system enforced.&lt;/p&gt;

&lt;p&gt;Once that happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;over-engineering dropped&lt;/li&gt;
&lt;li&gt;scope creep became visible&lt;/li&gt;
&lt;li&gt;refactoring felt safer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not because I tried harder, but because deviation became obvious.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design Became Simpler (Not Bigger)
&lt;/h2&gt;

&lt;p&gt;I expected specifications to lead to heavier designs.&lt;/p&gt;

&lt;p&gt;The opposite happened.&lt;/p&gt;

&lt;p&gt;Because the behavioral contract was clear, the design only needed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;satisfy the requirement&lt;/li&gt;
&lt;li&gt;handle failures&lt;/li&gt;
&lt;li&gt;remain replaceable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No speculative abstractions.&lt;br&gt;&lt;br&gt;
No future-proof layers.&lt;/p&gt;

&lt;p&gt;Design stopped being a guessing game and became a direct response to the spec.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tasks Stopped Being Vague
&lt;/h2&gt;

&lt;p&gt;Breaking the work into tasks became almost mechanical.&lt;/p&gt;

&lt;p&gt;Each task:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mapped to an acceptance criterion&lt;/li&gt;
&lt;li&gt;had a clear definition of done&lt;/li&gt;
&lt;li&gt;avoided vague verbs like “handle” or “manage”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Estimation improved.&lt;br&gt;&lt;br&gt;
Progress became visible.&lt;br&gt;&lt;br&gt;
Half-done work became obvious.&lt;/p&gt;

&lt;p&gt;Tasks stopped being things to do and became contracts to fulfill.&lt;/p&gt;




&lt;h2&gt;
  
  
  Refactoring Suddenly Felt Safe
&lt;/h2&gt;

&lt;p&gt;This part surprised me.&lt;/p&gt;

&lt;p&gt;Because the specification defined what must happen, not how, I could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rewrite internals&lt;/li&gt;
&lt;li&gt;simplify logic&lt;/li&gt;
&lt;li&gt;replace components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As long as the acceptance criteria still held, the system was correct.&lt;/p&gt;

&lt;p&gt;It changed how I view code:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Code is temporary.&lt;br&gt;&lt;br&gt;
Behavior is the real asset.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Mistakes I Made Along the Way
&lt;/h2&gt;

&lt;p&gt;It was not perfect. I made mistakes, and they were useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. I Over-Specified Security Details
&lt;/h3&gt;

&lt;p&gt;My first version included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;key formats&lt;/li&gt;
&lt;li&gt;cryptographic assumptions&lt;/li&gt;
&lt;li&gt;token strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That made the spec rigid and harder to evolve.&lt;/p&gt;

&lt;p&gt;Lesson learned: specify security outcomes, not mechanisms.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. I Treated the First Spec as Final
&lt;/h3&gt;

&lt;p&gt;I assumed the spec needed to be perfect from day one.&lt;/p&gt;

&lt;p&gt;It did not.&lt;/p&gt;

&lt;p&gt;The best specs evolved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wording improved&lt;/li&gt;
&lt;li&gt;acceptance criteria were reordered&lt;/li&gt;
&lt;li&gt;unnecessary constraints were removed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A spec should be living, not frozen.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. I Underestimated Acceptance Criteria
&lt;/h3&gt;

&lt;p&gt;At first, I treated acceptance criteria like a formality.&lt;/p&gt;

&lt;p&gt;They turned out to be the most valuable part of the entire process.&lt;/p&gt;

&lt;p&gt;If I had to keep only one thing from the spec, it would be those.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. I Almost Skipped Specs for Small Features
&lt;/h3&gt;

&lt;p&gt;I thought some features were too small to justify a spec.&lt;/p&gt;

&lt;p&gt;Those were exactly the ones that later caused confusion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unclear defaults&lt;/li&gt;
&lt;li&gt;inconsistent behavior&lt;/li&gt;
&lt;li&gt;surprising edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even a short spec was cheaper than fixing misunderstandings later.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Changed in My Workflow
&lt;/h2&gt;

&lt;p&gt;Using specification-driven development consistently led to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fewer late surprises&lt;/li&gt;
&lt;li&gt;fewer abandoned implementations&lt;/li&gt;
&lt;li&gt;fewer “why did we build this?” moments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I now spend more time at the beginning and far less time undoing work later.&lt;/p&gt;

&lt;p&gt;It does not slow me down.&lt;br&gt;&lt;br&gt;
It makes me deliberate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Kiro did not invent good engineering practices.&lt;/p&gt;

&lt;p&gt;It made them unavoidable.&lt;/p&gt;

&lt;p&gt;It did not teach me how to write better code.&lt;br&gt;&lt;br&gt;
It taught me how to think before writing code.&lt;/p&gt;

&lt;p&gt;And that turns out to be the hardest and most valuable part of engineering.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Writing code is easy.&lt;br&gt;&lt;br&gt;
Deciding what code should exist is the real work.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>kiro</category>
    </item>
    <item>
      <title>Cut Your AWS Lambda Logging Costs: Filter Logs with AWS SAM</title>
      <dc:creator>Firdaws Aboulaye</dc:creator>
      <pubDate>Sat, 28 Dec 2024 07:00:00 +0000</pubDate>
      <link>https://dev.to/faboulaye/cut-your-aws-lambda-logging-costs-filter-logs-with-aws-sam-1ap3</link>
      <guid>https://dev.to/faboulaye/cut-your-aws-lambda-logging-costs-filter-logs-with-aws-sam-1ap3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;AWS Lambda is a serverless compute service that scales automatically based on demand. However, the pay-per-use model extends to your logs as well—excessive logging can significantly increase your &lt;strong&gt;Amazon CloudWatch Logs&lt;/strong&gt; costs. In this article, we’ll look at how to configure and filter Lambda logs using &lt;strong&gt;AWS SAM (Serverless Application Model)&lt;/strong&gt; so you capture only the logs you truly need, especially during periods of high load.&lt;/p&gt;




&lt;h3&gt;
  
  
  Default Logging in AWS Lambda
&lt;/h3&gt;

&lt;p&gt;By default, Lambda writes &lt;strong&gt;plain text&lt;/strong&gt; logs to CloudWatch. While this is convenient, it can make filtering specific log levels (e.g., &lt;code&gt;DEBUG&lt;/code&gt;, &lt;code&gt;INFO&lt;/code&gt;, &lt;code&gt;WARN&lt;/code&gt;, &lt;code&gt;ERROR&lt;/code&gt;) difficult, potentially creating large volumes of unnecessary logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample Node.js Lambda
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="o"&gt;=&amp;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;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEBUG: This is a debug message.&lt;/span&gt;&lt;span class="dl"&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INFO: This is an informational message.&lt;/span&gt;&lt;span class="dl"&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="s2"&gt;WARN: This is a warning message.&lt;/span&gt;&lt;span class="dl"&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: This is an error message.&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="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello from Lambda!&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;h3&gt;
  
  
  AWS SAM function configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;NodeRuntime&lt;/span&gt;
    &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.handler&lt;/span&gt;
    &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;App&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;App&lt;/span&gt;
      &lt;span class="na"&gt;Project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;Project&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="c1"&gt;# Functions&lt;/span&gt;
  &lt;span class="na"&gt;FilteringLogsFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filtering-logs&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s"&gt;${App}-filtering-logs&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Filter logs by log level&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;strong&gt;CloudWatch&lt;/strong&gt;, you might see something like:&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%2Fvyhzrjvxwtlm4j0m11am.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%2Fvyhzrjvxwtlm4j0m11am.png" alt="AWS Cloudwatch logs with plain text format" width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All entries appear as plain text, making it difficult to filter precisely—and potentially driving up your log storage costs.&lt;/p&gt;




&lt;h3&gt;
  
  
  Configuring Logs with AWS SAM
&lt;/h3&gt;

&lt;p&gt;To better control which logs get stored, you can specify &lt;strong&gt;&lt;code&gt;LoggingConfig&lt;/code&gt;&lt;/strong&gt; in your SAM template as described &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-loggingconfig.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Pay attention this configuration is not supported in the &lt;code&gt;Global&lt;/code&gt; section of SAM template.&lt;br&gt;
This configuration helps you define log levels, format, and the target log group in a more structured way, so you don’t end up paying for excessive logs especially during high load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SAM Template&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;FilteringLogsFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filtering-logs&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s"&gt;${App}-filtering-logs&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Filter logs by log level&lt;/span&gt;
      &lt;span class="na"&gt;LoggingConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ApplicationLogLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;INFO&lt;/span&gt;
        &lt;span class="na"&gt;LogFormat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;JSON&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What These Settings Do
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ApplicationLogLevel&lt;/strong&gt;: Filters application-level logs (e.g., &lt;code&gt;DEBUG&lt;/code&gt;, &lt;code&gt;INFO&lt;/code&gt;, &lt;code&gt;WARN&lt;/code&gt;, &lt;code&gt;ERROR&lt;/code&gt;). Anything below this level won’t be logged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LogFormat&lt;/strong&gt;: Possible values are &lt;code&gt;Text&lt;/code&gt; (default) or &lt;code&gt;JSON&lt;/code&gt;. When using &lt;code&gt;Text&lt;/code&gt;, filtering by log level is not supported, which can lead to higher logging costs. Switching to &lt;code&gt;JSON&lt;/code&gt; enables log-level filtering and makes logs more structured.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In &lt;strong&gt;CloudWatch&lt;/strong&gt;, you might see something like:&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%2Frxob0r27nnjw6h51th4o.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%2Frxob0r27nnjw6h51th4o.png" alt="AWS Cloudwatch logs with JSON format" width="800" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By switching from the default &lt;strong&gt;&lt;code&gt;Text&lt;/code&gt;&lt;/strong&gt; format to &lt;strong&gt;&lt;code&gt;JSON&lt;/code&gt;&lt;/strong&gt; and setting appropriate log levels, you can drastically reduce log volume and costs during high load. Structured logs are also much easier to parse and filter in &lt;strong&gt;CloudWatch Logs Insights&lt;/strong&gt; or other monitoring tools.&lt;/p&gt;




&lt;h3&gt;
  
  
  Additional Tools for Advanced Logging
&lt;/h3&gt;

&lt;p&gt;While SAM’s built-in &lt;code&gt;LoggingConfig&lt;/code&gt; is powerful, you may want even more control over your logging. Libraries such as &lt;strong&gt;AWS Lambda Powertools&lt;/strong&gt; (available for multiple runtimes e.g. &lt;a href="https://docs.powertools.aws.dev/lambda/python/latest/" rel="noopener noreferrer"&gt;python&lt;/a&gt;) provide additional features like structured logging, tracing, and correlation IDs. These can help you further refine and filter your logs, improve observability, and reduce costs by focusing on the most important information.&lt;/p&gt;




&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Logging is essential for monitoring and troubleshooting AWS Lambda functions, but excessive logs can quickly inflate costs. By leveraging &lt;strong&gt;AWS SAM&lt;/strong&gt; and its &lt;strong&gt;&lt;code&gt;LoggingConfig&lt;/code&gt;&lt;/strong&gt; parameters—especially switching to &lt;code&gt;JSON&lt;/code&gt; and setting appropriate log levels—you can store only the logs that matter most. In addition, libraries like &lt;strong&gt;AWS Lambda Powertools&lt;/strong&gt; can offer even more granular control. This approach not only cuts costs but also streamlines your troubleshooting and monitoring process.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>sam</category>
      <category>logs</category>
    </item>
    <item>
      <title>Highly scalable image storage solution with AWS Serverless - Building File API for Uploads and Downloads</title>
      <dc:creator>Firdaws Aboulaye</dc:creator>
      <pubDate>Sat, 21 Dec 2024 07:00:00 +0000</pubDate>
      <link>https://dev.to/faboulaye/highly-scalable-image-storage-solution-with-aws-serverless-building-file-api-for-uploads-and-2kgb</link>
      <guid>https://dev.to/faboulaye/highly-scalable-image-storage-solution-with-aws-serverless-building-file-api-for-uploads-and-2kgb</guid>
      <description>&lt;p&gt;The &lt;strong&gt;File API&lt;/strong&gt; is essential for managing file uploads and downloads in our architecture. We designed a workflow that includes registration, secure uploads, and file state updates to handle file storage efficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Workflow Overview&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Registration&lt;/strong&gt;: Store file information (metadata, state, user or session information) in the system before the upload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Presigned URL Generation&lt;/strong&gt;: Provide a short-lived presigned URL for secure, direct uploads to Amazon S3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File State Update&lt;/strong&gt;: After the upload, update the file's state and verify its integrity.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Challenges and Solutions&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Initial Approach: Using S3 Events&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Method&lt;/strong&gt;: Each file upload to S3 triggered an S3 Event, invoking a Lambda function per file to update the database and check for corruption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issues&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High Concurrency&lt;/strong&gt;: Massive uploads led to exceeding AWS Lambda's concurrency limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Increased Errors&lt;/strong&gt;: Overloaded Lambdas resulted in failed executions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of Retries&lt;/strong&gt;: S3 Events didn't support easy reprocessing of failed events.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&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%2F4hzftaucwdppub0x9l9s.jpg" 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%2F4hzftaucwdppub0x9l9s.jpg" alt="Trigger post upload with S3 event design" width="676" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved Approach: Leveraging SQS with Batching&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Method&lt;/strong&gt;: Replaced S3 Events with messages sent to an SQS queue upon file upload. Configured Lambda functions to process batches of events from the queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benefits&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Executions&lt;/strong&gt;: Batch processing minimized the number of Lambda invocations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Error Handling&lt;/strong&gt;: SQS allowed &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/services-sqs-errorhandling.html" rel="noopener noreferrer"&gt;retries for failed messages&lt;/a&gt; with partial batch response&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: SQS standard queue has nearly unlimited throughput.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&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%2Ffokm66199ijis40034m1.jpg" 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%2Ffokm66199ijis40034m1.jpg" alt="Trigger post upload with SQS event design" width="676" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This architecture ensures scalability, reliability, and efficient handling of large volumes of uploads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;SQSEvent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SQS&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;Queue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;PostUploadHandlingSQSQueue.Arn&lt;/span&gt;
      &lt;span class="na"&gt;FunctionResponseTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReportBatchItemFailures&lt;/span&gt;
      &lt;span class="na"&gt;BatchSize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration ensures optimized SQS message processing and robust scalability, even during high traffic periods.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;BatchSize&lt;/code&gt; property in the Lambda definition, as shown below, allows customizing the maximum number of items retrieved per batch (e.g., &lt;code&gt;BatchSize: 9&lt;/code&gt;)&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The evolution of our File API showcases the importance of adapting architecture to meet real-world demands. By moving from direct S3 Event triggers to an SQS-based batch processing system, we overcame concurrency limits, reduced errors, and improved scalability.&lt;/p&gt;

&lt;p&gt;In the next article, we’ll explore another domain API, diving into its unique challenges and the solutions we implemented to address them. Stay tuned for more insights into our journey of building scalable and reliable APIs!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>filestorage</category>
      <category>cloudcomputing</category>
    </item>
    <item>
      <title>Highly scalable image storage solution with AWS Serverless - architectural decisions</title>
      <dc:creator>Firdaws Aboulaye</dc:creator>
      <pubDate>Tue, 10 Dec 2024 07:00:00 +0000</pubDate>
      <link>https://dev.to/faboulaye/highly-scalable-image-storagesolution-with-aws-serverless-architectural-decisions-2pba</link>
      <guid>https://dev.to/faboulaye/highly-scalable-image-storagesolution-with-aws-serverless-architectural-decisions-2pba</guid>
      <description>&lt;h3&gt;
  
  
  &lt;strong&gt;From Monolith to Domains: Architecting a Scalable Solution&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In our &lt;a href="https://dev.to/faboulaye/highly-scalable-image-storage-solution-with-aws-serverless-at-iplabs-part-1-3ep8"&gt;previous article&lt;/a&gt;, we introduced the motivation behind redesigning our image storage solution and the transition to a serverless-first approach. This follow-up focuses on the architectural challenges we faced while implementing the solution and how we overcame them.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Challenge 1: Splitting the Service by Domain&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Initially, our legacy system was a monolithic structure, tightly coupled and challenging to scale or extend. To modernize, we began by breaking the solution into domains and APIs, each handling specific responsibilities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;File API&lt;/strong&gt;: Responsible for handling image uploads, downloads, and storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project API&lt;/strong&gt;: Facilitating project save/load workflows for users working on designs like photo books.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduler Events&lt;/strong&gt;: Automating processes like project expiration, cleanup, and background tasks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each domain directly tied into the workflows outlined in the previous article, ensuring that the solution remained cohesive yet modular. This separation allowed us to independently scale, test, and enhance each domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Challenge 2: Lambda scalability and relational database connection bottlenecks&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To implement these APIs, we started with a straightforward and efficient architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt;: Serving as the entry point for client requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda&lt;/strong&gt;: Running serverless functions to process requests and business logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aurora Serverless v1&lt;/strong&gt;: Providing relational database capabilities for data storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Amazon Aurora Serverless v1 with Data API was chosen for its PostgreSQL compatibility and to avoid deploying Lambdas into a VPC or using RDS Proxy. However, the &lt;strong&gt;1000 requests per second limit&lt;/strong&gt; for the Data API (see &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/CHAP_Limits.html" rel="noopener noreferrer"&gt;AWS limits&lt;/a&gt;) became a bottleneck as usage grew. Aurora Serverless v2, available at the time, lacked Data API support (introduced in December 2023). To overcome these limitations, DynamoDB was adopted for its unlimited scalability and seamless serverless integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Solution: Switching to DynamoDB&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To resolve the database bottleneck, we migrated to &lt;strong&gt;Amazon DynamoDB&lt;/strong&gt;, which provided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unlimited scalability&lt;/strong&gt;: Handling concurrent connections efficiently without manual provisioning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High availability&lt;/strong&gt;: Reducing latency for global users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This shift significantly improved our system's performance and ensured that the APIs could handle rapid spikes in traffic. However, it also required &lt;strong&gt;significant learning efforts&lt;/strong&gt; as we needed to understand the concepts of NoSQL databases. Specifically, we had to master handling &lt;strong&gt;1-to-n&lt;/strong&gt; and &lt;strong&gt;n-to-m relationships&lt;/strong&gt;, as well as implementing &lt;strong&gt;single-table design patterns&lt;/strong&gt;, which are fundamental to designing efficient and scalable NoSQL solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Challenge 3: Choosing the Right API Gateway&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Our initial implementation utilized &lt;strong&gt;HTTP APIs&lt;/strong&gt; on API Gateway, which offered impressive performance and lower costs. However, as we integrated more advanced features, particularly around user authorization, we encountered limitations.&lt;/p&gt;

&lt;p&gt;To address these, we switched to &lt;strong&gt;REST APIs&lt;/strong&gt;, which provided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced integration with custom authorizers&lt;/strong&gt;: Streamlining secure access to resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Greater flexibility in routing&lt;/strong&gt;: Supporting complex workflows between domains.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The switch allowed us to maintain the necessary performance while adding advanced features for authentication and access control. If you're deciding between &lt;strong&gt;REST APIs&lt;/strong&gt; and &lt;strong&gt;HTTP APIs&lt;/strong&gt; for your project, refer to &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html" rel="noopener noreferrer"&gt;this AWS documentation&lt;/a&gt; for a detailed comparison of their capabilities and use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Outcome&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;By addressing these challenges step-by-step, our architecture evolved into a robust, scalable solution that could support our workflows efficiently:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Domains&lt;/strong&gt;: Independently scalable APIs for File, Project, and Scheduler domains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: A highly scalable and reliable backend powered by DynamoDB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt;: REST APIs for secure and feature-rich routing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This architecture empowered us to meet our business goals while preparing for future feature integrations and user demands.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The journey from a monolithic system to a domain-driven architecture was not without its hurdles. However, each challenge—from database scalability to API design—provided valuable insights that strengthened our solution. In the next article, we’ll explore how we leveraged this architecture to optimize workflows and introduce new features seamlessly.&lt;/p&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>filestorage</category>
    </item>
    <item>
      <title>Highly scalable image storage solution with AWS Serverless at ip.labs - Part 1</title>
      <dc:creator>Firdaws Aboulaye</dc:creator>
      <pubDate>Tue, 12 Nov 2024 07:00:00 +0000</pubDate>
      <link>https://dev.to/faboulaye/highly-scalable-image-storage-solution-with-aws-serverless-at-iplabs-part-1-3ep8</link>
      <guid>https://dev.to/faboulaye/highly-scalable-image-storage-solution-with-aws-serverless-at-iplabs-part-1-3ep8</guid>
      <description>&lt;p&gt;&lt;strong&gt;Co-Author:&lt;/strong&gt; &lt;a href="https://dev.to/vkazulkin"&gt;Vadym Kazulkin&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.iplabs.com/" rel="noopener noreferrer"&gt;ip.labs&lt;/a&gt;, a 100% subsidiary of the FUJIFILM Group headquartered in Bonn, is a global leader in white-label e-commerce software for imaging. Our software enables end consumers to design and purchase a variety of photo products, including prints, posters, photo books, calendars, greeting cards, and personalized gifts. In addition, we provide integrations for payment systems, single sign-on (SSO) solutions, and a unique API that allows providers to create a shared cart, facilitating seamless addition of our solution alongside their existing business.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Journey to the Cloud&lt;/strong&gt;
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Initial Setup and Challenges&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Until 2017, ip.labs managed several on-premises data centers across the globe, operating a large monolithic architecture that relied on a centralized database and NFS storage. While effective initially, this setup presented significant challenges, including scalability limitations, central dependency on infrastructure, and integration hurdles.&lt;/p&gt;

&lt;p&gt;Recognizing the need for greater agility, we decided to fully transition to the cloud. AWS was selected as our cloud partner, and by 2021, all customers had been migrated to AWS, allowing us to shut down our data centers completely. The migration began as a “lift and shift” approach but soon evolved to incorporate several key improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt; at both the infrastructure and deployment levels&lt;/li&gt;
&lt;li&gt;Adoption of &lt;strong&gt;immutable infrastructure&lt;/strong&gt; to eliminate hot patches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blue-Green deployment&lt;/strong&gt; strategies to minimize downtime&lt;/li&gt;
&lt;li&gt;Introduction of independent core microservices, fostering a “you build it, you run it” mindset&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-scaling policies&lt;/strong&gt; to efficiently manage workload variations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Legacy Image Storage Issues&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The cloud migration exposed several issues with our legacy image storage solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reliance on &lt;strong&gt;central AWS EFS&lt;/strong&gt; for image uploads/downloads from all EC2 servers, which created scalability and availability risks&lt;/li&gt;
&lt;li&gt;Lack of &lt;strong&gt;defined APIs&lt;/strong&gt; and limited &lt;strong&gt;testability&lt;/strong&gt; for image storage&lt;/li&gt;
&lt;li&gt;No clearly ownership for image storage solution&lt;/li&gt;
&lt;li&gt;Was still a part of the monolithic solution and was tightly coupled into many aspects of the self-written e-commerce system which made  integrations to the 3rd party providers really hard&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Move to a New Image Storage Solution&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To address these issues and support future image-related features, we made the strategic decision to develop a standalone, API-driven image storage service. Since handling images is our  core business, we opted to build our own solution rather than relying on third-party file storage providers.&lt;/p&gt;

&lt;p&gt;This new image storage system supports various workflows, including:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Standard Uploads:&lt;/strong&gt; Local uploads directly from users’ devices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile Uploads:&lt;/strong&gt; QR code-based uploads with optional long-term storage that extends beyond typical session duration (usually six hours).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project Save and Load:&lt;/strong&gt; A feature that allows users to save intermediate designs, useful for more time-intensive products like photo books.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project Sharing:&lt;/strong&gt; Users can share saved projects in various modes, allowing recipients to create copies. Future plans include collaborative editing and commenting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cart Item Persistence:&lt;/strong&gt; Cart items, both internal and external, are saved across multiple days to support integration with shared/external carts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order Storage:&lt;/strong&gt; Orders are stored for a defined period, enabling re-submission to professional labs for production and delivery in case of issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated and Manual Cleanup:&lt;/strong&gt; Image deletion is initiated either manually by the user or automatically upon project expiration or user logout.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Serverless Transformation: Why AWS Serverless?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Our new file storage solution, built with AWS Serverless technologies, is now rolled out to nearly all customers. This shift has not only enhanced our storage capabilities but has also paved the way for rapid feature development.&lt;/p&gt;

&lt;p&gt;We chose AWS Serverless for several reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Since 2018, we have extensively experimented with AWS Serverless across small to medium applications, experiencing firsthand the benefits of AWS-managed services  (Both authors of this series have given many talks about our journey at different conferences or meet ups since then).&lt;/li&gt;
&lt;li&gt;AWS Serverless enables rapid delivery, allowing our team to focus more on core capabilities.&lt;/li&gt;
&lt;li&gt;Adopting a &lt;strong&gt;Serverless-first approach&lt;/strong&gt; has fostered a culture of learning and knowledge-sharing within our development teams, further solidifying AWS Serverless as our preferred development model.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Advantages of the New Implementation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Our new image storage solution, built on AWS Serverless, offers several significant advantages over our previous monolithic architecture. Key benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scalability:&lt;/strong&gt; Leveraging AWS Serverless allows us to automatically scale resources based on demand. This elasticity ensures that our system remains performant even during peak usage, providing a seamless experience for end users while reducing the need for manual scaling efforts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility for Future Expansion:&lt;/strong&gt; The modular, API-driven architecture makes it easy to extend our file storage to other solutions. This flexibility supports faster adaptation to new business needs and allows for smooth integration with both our own services and external partner platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Feature Integration:&lt;/strong&gt; With the serverless approach, we’ve been able to introduce new features more efficiently, such as mobile uploads and project sharing. These functionalities allow users to conveniently upload photos from any device and share projects with others, all seamlessly integrated into our new storage system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This new implementation positions us well to continue adding value to our offerings while maintaining a highly scalable, flexible, and feature-rich platform&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This article has introduced the foundation of our AWS journey at ip.labs and our shift to a Serverless-first approach to redesign our image storage solution. In the upcoming series, we will explore the architecture of this new storage solution, diving into the details of the supporting APIs and the design choices behind them. &lt;/p&gt;

&lt;p&gt;See below a short overview of this 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%2F7qcpnew9enjq1ll3pgeo.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%2F7qcpnew9enjq1ll3pgeo.png" alt="Architecture of the solution" width="800" height="1338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stay tuned for the next article, where we’ll break down the architecture and functionality of the image storage solution’s APIs.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>filestorage</category>
      <category>cloudcomputing</category>
    </item>
  </channel>
</rss>
