<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>rafael &#8211; Rafael Bernard Araujo</title>
	<atom:link href="https://rafael.bernard-araujo.com/author/rafael/feed" rel="self" type="application/rss+xml" />
	<link>https://rafael.bernard-araujo.com</link>
	<description>desenvolvendo... while(!success){  try(); }</description>
	<lastBuildDate>Tue, 19 May 2026 00:58:20 +0000</lastBuildDate>
	<language>pt-BR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
<site xmlns="com-wordpress:feed-additions:1">21941730</site>	<item>
		<title>Building Evolutionary Architectures &#8211; Chapter 2: Fitness Functions</title>
		<link>https://rafael.bernard-araujo.com/building-evolutionary-architectures-chapter-2-fitness-functions.php</link>
					<comments>https://rafael.bernard-araujo.com/building-evolutionary-architectures-chapter-2-fitness-functions.php#respond</comments>
		
		<dc:creator><![CDATA[rafael]]></dc:creator>
		<pubDate>Tue, 19 May 2026 00:58:20 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[fitness functions]]></category>
		<category><![CDATA[software architecture]]></category>
		<guid isPermaLink="false">https://rafael.bernard-araujo.com/?p=2376</guid>

					<description><![CDATA[Chapter 2 introduces the concept of architectural fitness functions, the mechanism that makes &#34;evolutionary&#34; more than a buzzword. The origin: borrowing from evolutionary computing The term comes from genetic algorithm design. In evolutionary computing, a fitness function defines what &#34;better&#34; means so that solutions can gradually emerge through small changes across generations. The classic example: [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Chapter 2 introduces the concept of <strong>architectural fitness functions</strong>, the mechanism that makes &quot;evolutionary&quot; more than a buzzword.</p>
<h2>The origin: borrowing from evolutionary computing</h2>
<p>The term comes from genetic algorithm design. In evolutionary computing, a fitness function defines what &quot;better&quot; means so that solutions can gradually emerge through small changes across generations. The classic example: when using a genetic algorithm to optimise wing design, the fitness function assesses wind resistance, weight, air flow, and other desirable characteristics. At each generation, the engineer asks: is this closer to or further away from the goal?</p>
<p>Ford, Parsons and Kua borrow this concept for software:</p>
<blockquote>
<p><strong>An architectural fitness function provides an objective integrity assessment of some architectural characteristic(s).</strong></p>
</blockquote>
<p>In software, fitness functions check that developers preserve important architectural characteristics; the &quot;-ilities&quot; architects care about: scalability, security, performance, maintainability, resilience.</p>
<h2>The core idea</h2>
<p>An evolutionary architecture supports <em>guided</em>, incremental change across multiple dimensions. The key word is <strong><em>guided</em></strong>. Without guidance, incremental change is just drift. Fitness functions are what provide the guidance.</p>
<p>The fitness function protects the various architectural characteristics required for the system. These requirements differ greatly across systems and organisations: some require intense security; others require significant throughput or low latency; others need resilience to failure. A crucial early architecture decision is to define which dimensions matter most for a given system, based on business drivers, technical capabilities, and scale.</p>
<h2>Why this matters</h2>
<p>Most teams have implicit architectural goals: &quot;the system should be fast&quot;, &quot;services should be loosely coupled&quot;, &quot;we should be secure&quot;. The problem is that implicit goals erode. Nobody notices the slow degradation until a characteristic has already failed.</p>
<p>Fitness functions make the implicit explicit. They turn architectural aspirations into verifiable checks. Automated where possible, manual where necessary.</p>
<p>A key insight: improving one architectural dimension can accidentally harm another. Improving performance with caching might harm data freshness or security. Fitness functions act as guardrails that detect these tradeoff violations before they reach production.</p>
<h2>Categorising fitness functions</h2>
<p>The book defines several dimensions for classifying fitness functions:</p>
<h3>Atomic vs Holistic</h3>
<ul>
<li><strong>Atomic</strong> — tests one particular aspect of the architecture in isolation. Example: a unit test checking for cyclic dependencies in a package, or a code metric that checks cyclomatic complexity.</li>
<li><strong>Holistic</strong> — tests a combination of architectural aspects, assessing interactions between different concerns. Example: testing the number of concurrent users within a certain latency range while caching is enabled — this simultaneously checks scalability and data freshness. Holistic functions are harder to build but capture what atomic ones miss.</li>
</ul>
<h3>Triggered vs Continuous vs Temporal</h3>
<ul>
<li><strong>Triggered</strong> — executed in response to a specific event: a developer running a unit test, a CI pipeline stage, a QA person performing exploratory testing.</li>
<li><strong>Continuous</strong> — constant verification of architectural aspects. Monitoring and alerting are the classic examples. Netflix's Chaos Monkey — which runs in production and randomly terminates instances — is a continuous holistic fitness function that forces teams to build resilient services.</li>
<li><strong>Temporal</strong> — have a particular time component. Example: a reminder to check whether important security updates have been performed, or a scheduled dependency check that alerts on outdated libraries.</li>
</ul>
<h3>Static vs Dynamic</h3>
<ul>
<li><strong>Static</strong> — fixed predefined acceptable values. Binary pass/fail (a unit test), or a threshold (latency must be &lt; 200ms).</li>
<li><strong>Dynamic</strong> — acceptable values depend on context. Acceptable latency might depend on actual system scale; security requirements might vary based on the regulatory environment.</li>
</ul>
<h3>Automated vs Manual</h3>
<ul>
<li><strong>Automated</strong> — unit tests, deployment pipeline checks, stress tests, chaos engineering. Ideally as much automation as possible.</li>
<li><strong>Manual</strong> — some things can't be automated (legal approval requirements, certain QA processes). Some things aren't automated <em>yet</em>. The goal is to push the boundary toward automation over time.</li>
</ul>
<h2>What fitness functions look like in practice</h2>
<p>Fitness functions encompass existing engineering practices but also extend beyond them:</p>
<table>
<thead>
<tr>
<th>Category</th>
<th>Examples</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Architecture tests</strong></td>
<td>phpat (PHP/PHPStan) or ts-arch (TypeScript) rules checking component dependencies, layer violations, naming conventions, import directionality</td>
<td>Atomic, triggered</td>
</tr>
<tr>
<td><strong>Code metrics</strong></td>
<td>Cyclomatic complexity thresholds, afferent/efferent coupling limits</td>
<td>Atomic, triggered</td>
</tr>
<tr>
<td><strong>Contract tests</strong></td>
<td>API contract verification ensuring requirements are met</td>
<td>Atomic, triggered</td>
</tr>
<tr>
<td><strong>Security scanning</strong></td>
<td>Vulnerability scanning, licence compliance checks for open-source dependencies</td>
<td>Atomic, triggered</td>
</tr>
<tr>
<td><strong>Performance testing</strong></td>
<td>Load tests validating latency SLOs under expected concurrency</td>
<td>Holistic, triggered</td>
</tr>
<tr>
<td><strong>Monitoring &amp; alerting</strong></td>
<td>p99 latency monitors, error rate thresholds, SLO compliance dashboards</td>
<td>Atomic/holistic, continuous</td>
</tr>
<tr>
<td><strong>Chaos engineering</strong></td>
<td>Netflix Simian Army — randomly terminating instances, availability zones, or entire regions</td>
<td>Holistic, continuous</td>
</tr>
<tr>
<td><strong>Security reviews</strong></td>
<td>Quarterly security audits, penetration testing</td>
<td>Holistic, manual/temporal</td>
</tr>
<tr>
<td><strong>Dependency freshness</strong></td>
<td>Scheduled checks for outdated libraries or security patches</td>
<td>Atomic, temporal</td>
</tr>
</tbody>
</table>
<p><strong>The best fitness functions are</strong> <strong>automated and triggered</strong>: they give feedback at the point of change, not weeks later. Place them in the deployment pipeline. Fast atomic functions early, slow holistic functions later.</p>
<h2>Deployment pipelines as the enforcement mechanism</h2>
<p>Fitness functions only work if they're part of the delivery workflow. The deployment pipeline is where they live:</p>
<ol>
<li><strong>Early stages</strong> — fast, atomic checks: architecture tests (phpat, ts-arch), code metrics, linting, security scanning, contract tests.</li>
<li><strong>Middle stages</strong> — integration and performance tests, holistic triggered functions.</li>
<li><strong>Later stages / production</strong> — continuous monitoring, chaos engineering, temporal reminders.</li>
</ol>
<p>As Thoughtworks puts it: <em>&quot;creating the desired fitness functions — and including them in appropriate delivery pipelines — communicates these metrics as an important aspect of enterprise architecture.&quot;</em></p>
<h2>The four layers of fitness (from NILUS)</h2>
<p>A useful framing from practice splits fitness functions across four layers:</p>
<ol>
<li><strong>Structural fitness</strong> — code dependencies, database access patterns, API contracts, service boundaries.</li>
<li><strong>Behavioural fitness</strong> — latency, resilience, throughput, consistency, recovery behaviour.</li>
<li><strong>Operational fitness</strong> — deployment independence, observability coverage, runbook readiness, SLO compliance.</li>
<li><strong>Semantic fitness</strong> — bounded context integrity, event naming quality, policy ownership, domain model consistency.</li>
</ol>
<p>Most teams start at structural (the easiest to automate) and never reach semantic. But <strong>semantic fitness functions</strong> (checking that your domain model remains coherent as it evolves) <strong>are often the most valuable for long-lived systems</strong>.</p>
<h2>Systems thinking</h2>
<p>Dr. Russell Ackoff's quote captures the deeper point:</p>
<blockquote>
<p>A system is never the sum of its parts. It is the product of the interaction of its parts.</p>
</blockquote>
<p>Fitness functions that only measure individual components miss the point. The interesting failures happen at integration boundaries — between services, between teams, between intentions and reality. Holistic fitness functions (end-to-end latency, deployment frequency, change failure rate) capture what atomic ones cannot.</p>
<h2>How I'm applying this</h2>
<p>This connects directly to work I care about:</p>
<ul>
<li><strong>Platform modernisations</strong> I've designed and implemented were operational fitness: bringing reliability through automated deployment pipelines, observability and monitoring, and runbook readiness. I just called it &quot;keeping things running.&quot;</li>
<li><strong>ADRs</strong> capture the <em>decisions</em>. Fitness functions verify those decisions are still holding. Decisions and verification go hand in hand.</li>
<li><strong>Kent Beck's Test Desiderata</strong> is itself a fitness function for test quality — a checklist of characteristics that tests should exhibit (isolated, deterministic, fast, behavioural, structure-insensitive, specific, predictive).</li>
<li><strong>DORA metrics</strong> (deployment frequency, lead time, change failure rate, MTTR) are fitness functions for delivery capability.</li>
<li><strong>Code health metrics</strong> (as described in the Loveholidays case from <a href="https://rafael.bernard-araujo.com/tropecando-120.php">Tropeçando 120</a>) are fitness functions that enabled their AI-first shift — they invested in code health metrics <em>before</em> adopting AI, which is exactly the fitness-function-first approach.</li>
<li><strong>phpat</strong> (PHP, as a PHPStan extension) and <strong>ts-arch</strong> (TypeScript) — writing architecture rules as unit tests that run in CI is the purest implementation of triggered atomic fitness functions.</li>
</ul>
<p>The pattern: define what matters, measure it, enforce it automatically, and revisit periodically. Architecture that can't be verified can't evolve — it can only decay.</p>
<h2>Further reading</h2>
<ul>
<li><a href="https://martinfowler.com/articles/evo-arch-forward.html">Foreword to Building Evolutionary Architectures</a> — Martin Fowler's foreword to the book, framing fitness functions as the mechanism to monitor architectural state in an evolutionary style.</li>
<li><a href="https://www.thoughtworks.com/en-gb/insights/articles/fitness-function-driven-development">Fitness function-driven development</a> — Paula Paul &amp; Rosemary Wang apply the TDD mindset to architecture: write the fitness function first, then develop to pass it.</li>
<li><a href="https://martinfowler.com/articles/fitness-functions-data-products.html">Governing data products using fitness functions</a> — Extending fitness functions into Data Mesh governance (2024).</li>
<li><a href="https://www.thoughtworks.com/en-ca/insights/books/building-evolutionaryarchitectures-second-edition">Building Evolutionary Architectures, 2nd Edition</a> — The book.</li>
</ul>
<hr />
<p><em>Part of my reading notes on <a href="https://rafael.bernard-araujo.com/building-evolutionary-architectures-notes.php">Building Evolutionary Architectures</a> (Ford, Parsons, Kua).</em></p>
]]></content:encoded>
					
					<wfw:commentRss>https://rafael.bernard-araujo.com/building-evolutionary-architectures-chapter-2-fitness-functions.php/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2376</post-id>	</item>
		<item>
		<title>Building Evolutionary Architectures Notes</title>
		<link>https://rafael.bernard-araujo.com/building-evolutionary-architectures-notes.php</link>
					<comments>https://rafael.bernard-araujo.com/building-evolutionary-architectures-notes.php#respond</comments>
		
		<dc:creator><![CDATA[rafael]]></dc:creator>
		<pubDate>Tue, 19 May 2026 00:30:15 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[software architecture]]></category>
		<guid isPermaLink="false">https://rafael.bernard-araujo.com/?p=2378</guid>

					<description><![CDATA[Notes Chapter 1: Software Architecture Despite our best efforts, software becomes harder to change over time. For a variety of reasons, the parts that comprise software systems defy easy modifications, becoming more brittle and intractable over time. Changes in software projects are usually driven by a reevaluation of functionality and/or scope. But another type of [&#8230;]]]></description>
										<content:encoded><![CDATA[<h2>Notes</h2>
<h3>Chapter 1: Software Architecture</h3>
<blockquote>
<p>Despite our best efforts, software becomes harder to change over time. For a variety of reasons, the parts that comprise software systems defy easy modifications, becoming more brittle and intractable over time. Changes in software projects are usually driven by a reevaluation of functionality and/or scope. But another type of change occurs outside the control of architects and long-term planners. Though architects like to be able to strategically plan for the future, the constantly changing software development ecosystem makes that difficult. Since we can't avoid change, we need to exploit it.</p>
</blockquote>
<p>— On <em>Evolutionary Architecture</em></p>
<blockquote>
<p>An evolutionary architecture supports guided, incremental changes across multiple dimensions.</p>
</blockquote>
<p>— Definition of <em>Evolutionary Architecture</em></p>
<h3>Related: Ralph Johnson on Architecture (via Fowler, 2003)</h3>
<p>These quotes from Ralph Johnson (from [[2026-04-24 - Martin Fowler - Who Needs an Architect|Who Needs an Architect?]]) are foundational to the ideas in this book:</p>
<blockquote>
<p>&quot;In most successful software projects, the expert developers working on that project have a shared understanding of the system design. This shared understanding is called 'architecture.' [...] the architecture only includes the components and interfaces that are understood by all the developers.&quot;</p>
</blockquote>
<p>— Architecture as a social construct, not a diagram.</p>
<blockquote>
<p>&quot;There is no theoretical reason that anything is hard to change about software. If you pick any one aspect of software then you can make it easy to change, but we don't know how to make everything easy to change. Making something easy to change makes the overall system a little more complex, and making everything easy to change makes the entire system very complex. Complexity is what makes software hard to change. That, and duplication.&quot;</p>
</blockquote>
<p>— The fundamental tension that evolutionary architectures try to navigate: change vs complexity.</p>
<blockquote>
<p>&quot;Software is not limited by physics, like buildings are. It is limited by imagination, by design, by organization. In short, it is limited by properties of people, not by properties of the world. 'We have met the enemy, and he is us.'&quot;</p>
</blockquote>
<p>— The constraint is us, not the technology.</p>
<h3>Chapter 2: Fitness Functions</h3>
<blockquote>
<p>An evolutionary architecture supports <em>guided</em>, incremental change across multiple dimensions.</p>
</blockquote>
<p>-- on FItness Functions, chapter 2</p>
<blockquote>
<p>The fitness function protects the various archutectural characteristics required for the system. The specific architectural requirements differ greatly across systems and organizations, based on business drivers, technical capabilities, and a host of other factors. Some systems require intense security; others require significant throughput factors.</p>
</blockquote>
<p>-- on Fitness Functions, chapter 2</p>
<blockquote>
<p>A system is never the sum of its parts. It is the product of the interaction of its parts.</p>
</blockquote>
<p>-- Dr. Russel Ackoff</p>
]]></content:encoded>
					
					<wfw:commentRss>https://rafael.bernard-araujo.com/building-evolutionary-architectures-notes.php/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2378</post-id>	</item>
		<item>
		<title>Tropeçando 120</title>
		<link>https://rafael.bernard-araujo.com/tropecando-120.php</link>
					<comments>https://rafael.bernard-araujo.com/tropecando-120.php#respond</comments>
		
		<dc:creator><![CDATA[rafael]]></dc:creator>
		<pubDate>Mon, 18 May 2026 00:02:13 +0000</pubDate>
				<category><![CDATA[Tropeçando]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[developer experience]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[machine learning]]></category>
		<category><![CDATA[software architecture]]></category>
		<category><![CDATA[software engineering]]></category>
		<guid isPermaLink="false">https://rafael.bernard-araujo.com/?p=2363</guid>

					<description><![CDATA[AI Management &#38; Organizational Restructuring The Foreman Problem: Managing Teams When Your Best Worker Isn't Human - Willian Correa Every major technology shift invented a new management role. Steam power → foreman. Office computing → project manager. Internet → product manager. AI is doing the same, but this time the failure mode is invisible: confident, [&#8230;]]]></description>
										<content:encoded><![CDATA[<h3>AI Management &amp; Organizational Restructuring</h3>
<p><a href="https://businessasusual.io/p/the-foreman-problem-managing-teams">The Foreman Problem: Managing Teams When Your Best Worker Isn't Human</a> - Willian Correa</p>
<blockquote>
<p>Every major technology shift invented a new management role. Steam power → foreman. Office computing → project manager. Internet → product manager. AI is doing the same, but this time the failure mode is invisible: confident, polished, wrong output. The new job is not directing effort but verifying that things that <em>look</em> like they're running actually are.</p>
</blockquote>
<p><a href="https://theengineeringmanager.substack.com/p/who-will-be-the-senior-engineers">Who Will Be the Senior Engineers of 2035?</a> - James Stanier</p>
<blockquote>
<p>The traditional junior-to-senior pipeline is breaking: entry-level tech postings down 67% since 2022, junior employment down ~20%. Firms adopting AI saw junior employment fall 7.7% vs non-adopters. 54% of engineering leaders plan to hire fewer juniors.</p>
</blockquote>
<h3>Compound Engineering &amp; Code Health</h3>
<p><a href="https://refactoring.fm/p/the-compounding-software-factory">The Compounding Software Factory</a> - Luca Rossi (Software Factory series, Part 3 of 3)</p>
<blockquote>
<p>What causes teams to degrade: poor coding hygiene (bad testing, poor code health, missing abstractions), failure to capture knowledge (no ADRs, no playbooks, no snapshots), and building the wrong things.</p>
</blockquote>
<p><a href="https://refactoring.fm/p/ai-coding-meets-code-health-with">AI Coding Meets Code Health</a> - Stuart Caborn</p>
<blockquote>
<p>Loveholidays' journey to becoming an AI-first engineering organization. Core thesis: code health is the foundation for successful AI adoption. By deliberately investing in code health metrics <em>before</em> adopting AI, they achieved 80+ deployments/month, 60% AI-written code, &lt;1% change failure rate, all while maintaining elite code health.</p>
</blockquote>
<h3>Security &amp; Infrastructure</h3>
<p><a href="https://www.wiz.io/blog/github-actions-security-ai-powered-actions-vulnerabilities">The (In)security Landscape of AI-Powered GitHub Actions</a> - Shay Berkovich</p>
<blockquote>
<p>AI-powered GitHub Actions from vendors like OpenAI, Anthropic, and Google are now running in thousands of public workflows. Research found bypasses of non-default configurations letting any external attacker trigger AI execution, a novel secret exfiltration vector for dynamically-created credential files, and widespread misconfigurations in production workflows.</p>
</blockquote>
<p><a href="https://www.allthingsdistributed.com/2026/04/the-invisible-engineering-behind-lambdas-network.html">The Invisible Engineering Behind Lambda's Network</a> - Werner Vogels</p>
<blockquote>
<p>A decade-long story of invisible infrastructure engineering by Lambda's networking team.</p>
</blockquote>
<h3>Career &amp; Token Economics</h3>
<p><a href="https://businessasual.io/p/tokenmaxxing-is-the-budget-game-played">Tokenmaxxing Is the Budget Game Played With AI Tokens</a> - Willian Correa</p>
<blockquote>
<p>Tokenmaxxing — maximising AI token consumption for visibility — is the corporate &quot;use it or lose it&quot; budget game in a new currency. Meta's internal &quot;Claudeonomics&quot; leaderboard ranked 85K employees by token consumption; top user burned 281B tokens in 30 days.</p>
</blockquote>
<h3>Tools</h3>
<p><a href="https://docs.docker.com/compose/how-tos/file-watch/">Use Compose Watch</a></p>
<p>Docker bind volumes gets a supercharge. Compose Watch does not replace bind mounts but exists as a companion specifically suited to developing in containers.</p>
<blockquote>
<p>More importantly, watch allows for greater granularity than is practical with a bind mount. Watch rules let you ignore specific files or entire directories within the watched tree.<br />
For example, in a Node.js project, it's not recommended to sync the node_modules/ directory. Even though JavaScript is interpreted, npm packages can contain native code that is not portable across platforms.</p>
</blockquote>
]]></content:encoded>
					
					<wfw:commentRss>https://rafael.bernard-araujo.com/tropecando-120.php/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2363</post-id>	</item>
		<item>
		<title>Geek</title>
		<link>https://rafael.bernard-araujo.com/geek.php</link>
					<comments>https://rafael.bernard-araujo.com/geek.php#respond</comments>
		
		<dc:creator><![CDATA[rafael]]></dc:creator>
		<pubDate>Wed, 22 Apr 2026 05:48:43 +0000</pubDate>
				<category><![CDATA[Miscelaneous]]></category>
		<guid isPermaLink="false">https://rafael.bernard-araujo.com/?p=2361</guid>

					<description><![CDATA[A &#34;geek&#34; is someone who is highly creative, highly technical, and highly attached to being both. -- GeePaw Hill]]></description>
										<content:encoded><![CDATA[<blockquote>
<p>A &quot;geek&quot; is someone who is highly creative, highly technical, and highly attached to being both.</p>
</blockquote>
<p>-- <a href="https://www.geepawhill.org/2018/01/29/geeks-and-geekery-why-i-use-these-words/">GeePaw Hill</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://rafael.bernard-araujo.com/geek.php/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2361</post-id>	</item>
		<item>
		<title>Introduce Parameter Object &#124; Refactoring Patterns</title>
		<link>https://rafael.bernard-araujo.com/introduce-parameter-object-refactoring-patterns.php</link>
					<comments>https://rafael.bernard-araujo.com/introduce-parameter-object-refactoring-patterns.php#respond</comments>
		
		<dc:creator><![CDATA[rafael]]></dc:creator>
		<pubDate>Wed, 22 Apr 2026 05:45:10 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[refactoring patterns]]></category>
		<category><![CDATA[rust]]></category>
		<category><![CDATA[software architecture]]></category>
		<guid isPermaLink="false">https://rafael.bernard-araujo.com/?p=2357</guid>

					<description><![CDATA[This refactoring pattern involves grouping parameters that naturally go together into a single object. When you see a group of data items that regularly travel together, appearing in function after function, it's a sign they should be combined into a single object. Check https://rafael.bernard-araujo.com/refactoring-patterns/introduce-parameter-object There are PHP and Rust implemenation examples.]]></description>
										<content:encoded><![CDATA[<p>This refactoring pattern involves grouping parameters that naturally go together into a single object. When you see a group of data items that regularly travel together, appearing in function after function, it's a sign they should be combined into a single object.</p>
<p>Check <a href="https://rafael.bernard-araujo.com/refactoring-patterns/introduce-parameter-object">https://rafael.bernard-araujo.com/refactoring-patterns/introduce-parameter-object</a></p>
<p>There are PHP and Rust implemenation examples.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://rafael.bernard-araujo.com/introduce-parameter-object-refactoring-patterns.php/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2357</post-id>	</item>
		<item>
		<title>Tropeçando 119</title>
		<link>https://rafael.bernard-araujo.com/tropecando-119.php</link>
					<comments>https://rafael.bernard-araujo.com/tropecando-119.php#respond</comments>
		
		<dc:creator><![CDATA[rafael]]></dc:creator>
		<pubDate>Tue, 21 Apr 2026 04:18:20 +0000</pubDate>
				<category><![CDATA[Tropeçando]]></category>
		<category><![CDATA[agent tools]]></category>
		<category><![CDATA[process management]]></category>
		<category><![CDATA[secrets management]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[serverless]]></category>
		<category><![CDATA[software engineering]]></category>
		<guid isPermaLink="false">https://rafael.bernard-araujo.com/?p=2350</guid>

					<description><![CDATA[How to Grow your Software Factory Luca Rossi argues that the right measure of AI effectiveness isn't lines of code but leverage — how much output you get per unit of human input. Teams progress through three stages: writing full specs for everything, then encoding knowledge into shared rules (like AGENTS.md), and finally building reusable [&#8230;]]]></description>
										<content:encoded><![CDATA[<p><a href="https://refactoring.fm/p/growing-your-sofware-factory">How to Grow your Software Factory</a> </p>
<p>Luca Rossi argues that the right measure of AI effectiveness isn't lines of code but leverage — how much output you get per unit of human input. Teams progress through three stages: writing full specs for everything, then encoding knowledge into shared rules (like AGENTS.md), and finally building reusable modules that enforce correctness by design.</p>
<p><a href="https://newsletter.theburningmonk.com/posts/the-security-case-for-serverless-just-got-stronger">The security case for serverless just got stronger</a></p>
<blockquote>
<p>AI agents can now scan an entire open-source codebase for exploitable vulnerabilities in hours.</p>
<p>Frontier models carry the complete library of known bug classes in their weights. So you can simply point an AI agent at a codebase and tell it to find zero-days.</p>
<p>This isn't theoretical.</p>
</blockquote>
<p>Yan Cui highlights that AI agents can now find real zero-days in open-source codebases at scale, shrinking the patch window from weeks to hours. Serverless and managed services have a structural advantage because AWS patches the runtime for you. The practical takeaways: eliminate long-lived AWS keys everywhere, treat LLM API keys like credentials, and scan your repos for exposed secrets.</p>
<p><a href="https://www.nodejs-security.com/blog/do-not-use-secrets-in-environment-variables-and-here-is-how-to-do-it-better">Do not use secrets in environment variables and here's how to do it better</a></p>
<p><a href="https://apenwarr.ca/log/20260316">Every Layer of Review Makes You 10x Slower</a></p>
<p>Each approval layer adds 10x wall clock time, and AI can't fix that. It only speeds up the first step. Drawing on Deming and the Toyota Production System, the argument is that review layers hide root causes rather than fixing them. The memorable line: <em>&quot;The job of a code reviewer isn't to review code — it's to figure out how to obsolete their review comment, that whole class of comment, forever.&quot;</em></p>
<p>The common thread across all four: the bottleneck isn't writing code, it's the systems around it. Whether it's review layers, security patching, or AI leverage, the answer is the same: engineer quality into the system itself through tests, automation, modules, and clear interfaces, rather than adding layers of inspection after the fact.</p>
<p><a href="https://www.theregister.com/2026/04/13/claude_code_cache_confusion/">Claude Code cache chaos creates quota complaints</a></p>
<p>Anthropic changed the prompt cache TTL from 1 hour to 5 minutes in March. Long, high-context sessions hit quota limits much faster. Pro users report as few as 2 prompts per 5 hours. Leaving your machine for &gt;1 hour = full cache miss on the 1M token context. They're considering reducing the default to 400K tokens.</p>
<p>Token consumption matters more than ever. The next two tools address this from both ends.</p>
<p><a href="https://juliusbrussee.github.io/caveman/">Caveman — Output Token Compression</a></p>
<p>Constrains LLM output to minimal-token structures. Strips pleasantries and padding, keeps code and technical content. Up to 87% output token reduction. Paper shows brevity constraints improve accuracy by 26pp.</p>
<p><a href="https://github.com/rtk-ai/rtk">RTK (Rust Token Killer) — Input Token Compression</a></p>
<p>Intercepts shell command outputs (git, ls, grep, test runners, docker, AWS CLI — 100+ commands) and compresses them before they reach the LLM context. 60-90% input token reduction, &lt; 10ms overhead.</p>
<p>Works with: Claude Code, Copilot, Gemini CLI, Codex, Cursor, Windsurf, Cline.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://rafael.bernard-araujo.com/tropecando-119.php/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2350</post-id>	</item>
		<item>
		<title>Tropeçando 118</title>
		<link>https://rafael.bernard-araujo.com/tropecando-118.php</link>
					<comments>https://rafael.bernard-araujo.com/tropecando-118.php#respond</comments>
		
		<dc:creator><![CDATA[rafael]]></dc:creator>
		<pubDate>Mon, 09 Mar 2026 02:47:29 +0000</pubDate>
				<category><![CDATA[Tropeçando]]></category>
		<category><![CDATA[ddd]]></category>
		<category><![CDATA[domain-driven design]]></category>
		<category><![CDATA[ia]]></category>
		<category><![CDATA[middleware]]></category>
		<category><![CDATA[software architecture]]></category>
		<category><![CDATA[software engineering]]></category>
		<guid isPermaLink="false">https://rafael.bernard-araujo.com/?p=2273</guid>

					<description><![CDATA[Your AI Coding agent doesn’t know when to ask for help Why do multi-agent coding systems fall apart on complex, real-world tasks? How to Manage Context in AI Coding Focus on building multiplayer, dynamic systems that provide the right information reliably, rather than crafting magical wording. Design workflows where AI can fetch what it needs [&#8230;]]]></description>
										<content:encoded><![CDATA[<p><a href="https://medium.com/@igorcosta/your-ai-coding-agent-doesnt-know-when-to-ask-for-help-75c10be496c5">Your AI Coding agent doesn’t know when to ask for help</a></p>
<blockquote>
<p>Why do multi-agent coding systems fall apart on complex, real-world tasks?</p>
</blockquote>
<p><a href="https://refactoring.fm/p/managing-context-for-ai-coding?utm_source=substack&amp;utm_campaign=post_embed&amp;utm_medium=web">How to Manage Context in AI Coding</a></p>
<blockquote>
<p>Focus on building multiplayer, dynamic systems that provide the right information reliably, rather than crafting magical wording. Design workflows where AI can fetch what it needs automatically.</p>
</blockquote>
<p><a href="https://martinfowler.com/bliki/ValueObject.html">Value Object</a></p>
<blockquote>
<p>When programming, I often find it's useful to represent things as a compound.</p>
</blockquote>
<p><a href="https://martinfowler.com/eaaDev/Range.html">Range - Further Enterprise Application Architecture development</a></p>
<blockquote>
<p>It's quite common to see comparisons where a value is checked against a range of values. Ranges are usually handled by a pair of values and you check against them both. Range instead uses a single object to represent the range as a whole, and then provides the relevant operations to test to see if values fall in the range and to compare ranges.</p>
</blockquote>
<p><a href="https://dzone.com/articles/jdk-memory-bloat-containers?email_hash=d5eba7663d4e04a1e0f4886103285fd8">JDK 17 Memory Bloat in Containers: A Post-Mortem</a></p>
<p>I just love runtime upgrades. Runtime upgrade are very important. And they need careful planning. Not unusual that they teach us important lessons for the next upgrade.</p>
<blockquote>
<p>When engineering teams modernize Java applications, the shift from JDK 8 to newer Long-Term Support (LTS) versions, such as JDK 11, 17, and soon 21, might seem straightforward at first. Since Java maintains backward compatibility, it's easy to assume that the runtime behavior will remain largely unchanged. However, that's far from reality.</p>
</blockquote>
<p><a href="https://tidyfirst.substack.com/p/my-fitbit-buzzed-and-i-understood?utm_source=post-email-title&amp;publication_id=256838&amp;post_id=180524868&amp;utm_campaign=email-post-title&amp;isFreemail=false&amp;r=6fpjy1&amp;triedRedirect=true&amp;utm_medium=email">My Fitbit Buzzed and I Understood Enshittification</a></p>
<blockquote>
<p>My Fitbit started buzzing at me a year ago. “It looks like you’re exercising.”</p>
<p>Product development is also an exercise in human relationships. And when we reduce those relationships to metrics, we lose something essential. We lose the ability to say, “This would be rude.” We lose the ability to treat users like people instead of engagement vectors.</p>
</blockquote>
<p><a href="https://maximegosselin.com/posts/using-the-middleware-pattern-to-extend-php-libraries/">Using the Middleware Pattern to Extend PHP Libraries</a></p>
<blockquote>
<p>PSR-15 did not invent middleware. But it showed the PHP community what a well-designed, typed middleware interface looks like. There is no reason to leave that idea at the HTTP layer.</p>
<p>If you maintain a PHP library with any non-trivial processing, consider building middleware support in from day one. Your users will thank you, and so will your future self.</p>
</blockquote>
]]></content:encoded>
					
					<wfw:commentRss>https://rafael.bernard-araujo.com/tropecando-118.php/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2273</post-id>	</item>
		<item>
		<title>Building a Serverless PHP Application with Bref, Symfony, and DynamoDB Session Management</title>
		<link>https://rafael.bernard-araujo.com/building-a-serverless-php-application-with-bref-symfony-and-dynamodb-session-management.php</link>
					<comments>https://rafael.bernard-araujo.com/building-a-serverless-php-application-with-bref-symfony-and-dynamodb-session-management.php#respond</comments>
		
		<dc:creator><![CDATA[rafael]]></dc:creator>
		<pubDate>Tue, 30 Dec 2025 07:48:35 +0000</pubDate>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Technology]]></category>
		<guid isPermaLink="false">https://rafael.bernard-araujo.com/?p=2283</guid>

					<description><![CDATA[Introduction Serverless apps are fantastic for automatic scaling, but there’s a catch: they expect you to be stateless. Most web applications, however, rely on sessions to remember users and persist state. Traditional PHP session handlers store data on the filesystem, which doesn’t play nicely with ephemeral AWS Lambda instances. Your sessions vanish as soon as [&#8230;]]]></description>
										<content:encoded><![CDATA[<h2>Introduction</h2>
<p>Serverless apps are fantastic for automatic scaling, but there’s a catch: they expect you to be stateless. Most web applications, however, rely on sessions to remember users and persist state. Traditional PHP session handlers store data on the filesystem, which doesn’t play nicely with ephemeral AWS Lambda instances. Your sessions vanish as soon as the instance disappears.</p>
<p>The usual fix? Fire up a Redis cluster. Works, but suddenly you’ve added infrastructure, ongoing maintenance, and extra costs. Your “serverless” app feels a lot less serverless.</p>
<p>What if we could manage sessions <strong>without touching Redis or any other server</strong>?</p>
<p>In this post, we’ll show you how to build a <strong>truly serverless PHP app</strong> using <strong>Bref</strong>, <strong>Symfony</strong>, and <strong>DynamoDB</strong> for session management. Along the way, you’ll see:</p>
<ul>
<li>A <strong>custom DynamoDB-backed session handler</strong> that replaces filesystem sessions</li>
<li>How to deploy your app via <strong>Lambda Function URLs</strong> using AWS CDK</li>
<li>Storing <strong>CSRF tokens in DynamoDB</strong> for fully stateless operation</li>
<li><strong>Single-table design patterns</strong> for efficient multi-entity storage</li>
</ul>
<p>By the end, you’ll know not just <em>how</em> to implement this architecture, but also <em>when</em> it makes sense and what trade-offs you’re accepting.</p>
<h2>The Challenge: Sessions in Serverless</h2>
<p>Before we dive into the solution, let’s understand why traditional PHP sessions fail in serverless environments.</p>
<ol>
<li><strong>Ephemeral Storage</strong>: Lambda instances can vanish at any time. Writing sessions to <code>/tmp</code> is like storing them in sand. They disappear when the instance is recycled.</li>
<li><strong>No Shared Filesystem</strong>: Each Lambda invocation runs on its own instance. User A’s session written by instance 1 is invisible to instance 2. That’s a problem if your user expects to stay logged in.</li>
<li><strong>Horizontal Scaling Woes</strong>: Lambda scales horizontally automatically. Without centralized session storage, each instance is isolated. Consistent session management? Forget it.</li>
</ol>
<h3>The Traditional Solution: Redis/ElastiCache</h3>
<p>Most serverless PHP guides suggest Redis. While it works, it comes with headaches:</p>
<ul>
<li><strong>Infrastructure complexity</strong>: VPCs, subnets, and security groups</li>
<li><strong>Maintenance burden</strong>: Patching, monitoring, capacity planning</li>
<li><strong>Cold start penalty</strong>: VPC-connected Lambdas can take 1–2 extra seconds</li>
</ul>
<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Better idea</strong>: DynamoDB. It’s fully managed, serverless, and scales automatically. No Redis cluster, no maintenance, just pay for what you use.</p>
<h2>Books and Authors App (Serverless Style)</h2>
<p>Imagine you’re building a multi-tenant SaaS app, like an internal tool for managing books and authors. Each user needs a session, and each organization manages its own data. DynamoDB’s single-table design can elegantly handle all this. Serverless scaling takes care of traffic spikes automatically.</p>
<p>Here’s what this example demonstrates:</p>
<ul>
<li><strong>Multi-entity relationships</strong>: Books belong to authors</li>
<li><strong>CRUD operations</strong>: Create, read, update, and delete across related entities</li>
<li><strong>Session-dependent workflows</strong>: Adding/editing books requires authentication</li>
<li><strong>Real-world complexity</strong>: More than a simple counter, less than a full e-commerce platform</li>
</ul>
<h3>Connecting to Real Use Cases</h3>
<p>This architecture shines in scenarios like:</p>
<ul>
<li><strong>Unpredictable traffic</strong>: Seasonal spikes when authors release new books</li>
<li><strong>Session management</strong>: Authors need persistent sessions to edit content</li>
<li><strong>Cost efficiency</strong>: During quiet periods, you pay pennies; during spikes, DynamoDB scales automatically</li>
<li><strong>Zero maintenance</strong>: No Redis clusters to monitor, no database servers to patch</li>
</ul>
<p>The book management example proves that this approach isn’t just theoretical: it’s production-ready.</p>
<h2>Architecture Overview</h2>
<p>To build a serverless PHP application that supports sessions, CSRF protection, and persistent data, we follow a <strong>stateful/stateless separation</strong> pattern. This makes the architecture scalable, cost-efficient, and easy to maintain.</p>
<h3>1. Stateful Layer: Persistent Data</h3>
<p>This layer is responsible for storing all data that needs to survive beyond a single Lambda invocation.</p>
<ul>
<li>
<p><strong>DynamoDB Table</strong></p>
<ul>
<li>Uses a <strong>single-table design</strong> to store sessions, CSRF tokens, users, books, and authors.</li>
<li><strong>TTL enabled</strong> for automatic session expiration.</li>
<li><strong>On-demand billing</strong> ensures automatic scaling with traffic.</li>
<li>Built-in <strong>multi-AZ replication</strong> provides high availability.</li>
</ul>
</li>
<li>
<p><strong>Benefits</strong></p>
<ul>
<li>No infrastructure to manage or patch.</li>
<li>Automatically scales with unpredictable traffic.</li>
<li>Centralized storage simplifies queries and operations.</li>
</ul>
</li>
</ul>
<h3>2. Stateless Layer: Application Logic</h3>
<p>This layer runs the application code and handles requests without storing any persistent state locally.</p>
<ul>
<li>
<p><strong>Lambda Function</strong></p>
<ul>
<li>Runs <strong>PHP-FPM</strong> via Bref.</li>
<li>Handles HTTP requests directly using a <strong>Lambda Function URL</strong> (HTTPS endpoint).</li>
<li>No VPC required to access DynamoDB, reducing cold start latency.</li>
</ul>
</li>
<li>
<p><strong>Static Assets</strong></p>
<ul>
<li>Stored in <strong>S3</strong> (optionally served via CloudFront) to keep Lambda stateless.</li>
</ul>
</li>
<li>
<p><strong>Benefits</strong></p>
<ul>
<li>Scales automatically with traffic.</li>
<li>Cost-efficient: pay only for actual requests.</li>
<li>Stateless logic simplifies deployment and updates.</li>
</ul>
</li>
</ul>
<p>This design ensures a <strong>truly serverless PHP application</strong> that handles session state, persistent data, and scalable workloads without the operational overhead of managing Redis or other caching layers.</p>
<h2>DynamoDB Session Handler and CSRF Implementation</h2>
<h3>Session Handler Implementation</h3>
<p>In a serverless PHP application, traditional session storage (files or local memory) doesn’t work because Lambda functions are <strong>ephemeral</strong>. Each invocation may run on a different container, so we need a centralized, persistent session store.</p>
<p>The core of our solution is a custom session handler that implements PHP's <code>SessionHandlerInterface</code>.</p>
<h4>How It Works</h4>
<ul>
<li><strong>Sessions are stored in DynamoDB</strong> instead of the filesystem.</li>
<li>Each session has a unique <code>session_id</code>, which becomes the partition key (<code>PK</code>) in DynamoDB.</li>
<li>Sessions include the serialized PHP session data and an <strong>expiration timestamp</strong> (TTL).</li>
<li>The handler automatically reads/writes session data on <code>session_start()</code> and <code>session_write_close()</code>.</li>
</ul>
<h4>Key Features</h4>
<ol>
<li><strong>Automatic Expiration</strong>
<ul>
<li>DynamoDB TTL ensures sessions are removed automatically after expiration.</li>
</ul>
</li>
<li><strong>Atomic Operations</strong>
<ul>
<li><code>PutItem</code> and <code>UpdateItem</code> guarantee consistent writes, even with concurrent requests.</li>
</ul>
</li>
<li><strong>Scalable</strong>
<ul>
<li>Can handle thousands of concurrent sessions without extra infrastructure.</li>
</ul>
</li>
<li><strong>Serverless-friendly</strong>
<ul>
<li>No local storage, no Redis, fully compatible with Lambda statelessness.</li>
</ul>
</li>
</ol>
<h4>Implementation</h4>
<pre><code class="language-php">&lt;?php

namespace App\Session;

use AsyncAws\DynamoDb\DynamoDbClient;
use AsyncAws\DynamoDb\Input\DeleteItemInput;
use AsyncAws\DynamoDb\Input\GetItemInput;
use AsyncAws\DynamoDb\Input\PutItemInput;
use AsyncAws\DynamoDb\ValueObject\AttributeValue;

/**
 * A minimal DynamoDB-backed PHP session handler using AsyncAws.
 *
 * Table design (single-table compatible):
 *  - PK: &quot;SESSION&quot;
 *  - SK: &quot;SID#&lt;session_id&gt;&quot;
 *  - data: base64-encoded session payload (string)
 *  - expiresAt: unix epoch seconds (number), enable DynamoDB TTL on this attribute
 *
 * Garbage collection is handled by DynamoDB&#039;s TTL, so gc() is a no-op.
 */
class DynamoDbSessionHandler implements \SessionHandlerInterface
{
    private const string PK_VALUE = &#039;SESSION&#039;;
    private const string SK_PREFIX = &#039;SID#&#039;;

    public function __construct(
        private readonly DynamoDbClient $dynamoDb,
        private readonly string $tableName,
        private readonly int $ttlSeconds = 3600,
    ) {}

    public function open(string $path, string $name): bool
    {
        // Nothing to do
        return true;
    }

    public function close(): bool
    {
        // Nothing to do
        return true;
    }

    public function read(string $id): string
    {
        $result = $this-&gt;dynamoDb-&gt;getItem(new GetItemInput([
            &#039;TableName&#039; =&gt; $this-&gt;tableName,
            &#039;Key&#039; =&gt; [
                &#039;PK&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; self::PK_VALUE]),
                &#039;SK&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; self::SK_PREFIX . $id]),
            ],
            // Strongly consistent read to reduce stale sessions
            &#039;ConsistentRead&#039; =&gt; true,
        ]));

        $item = $result-&gt;getItem();
        if (!$item || !isset($item[&#039;data&#039;])) {
            return &#039;&#039;;
        }

        $encoded = $item[&#039;data&#039;]-&gt;getS();
        if ($encoded === null) {
            return &#039;&#039;;
        }

        $payload = base64_decode($encoded, true);
        return $payload === false ? &#039;&#039; : $payload;
    }

    public function write(string $id, string $data): bool
    {
        $expiresAt = time() + $this-&gt;ttlSeconds;

        $this-&gt;dynamoDb-&gt;putItem(new PutItemInput([
            &#039;TableName&#039; =&gt; $this-&gt;tableName,
            &#039;Item&#039; =&gt; [
                &#039;PK&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; self::PK_VALUE]),
                &#039;SK&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; self::SK_PREFIX . $id]),
                &#039;data&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; base64_encode($data)]),
                &#039;expiresAt&#039; =&gt; new AttributeValue([&#039;N&#039; =&gt; (string) $expiresAt]),
            ],
        ]));

        return true;
    }

    public function destroy(string $id): bool
    {
        $this-&gt;dynamoDb-&gt;deleteItem(new DeleteItemInput([
            &#039;TableName&#039; =&gt; $this-&gt;tableName,
            &#039;Key&#039; =&gt; [
                &#039;PK&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; self::PK_VALUE]),
                &#039;SK&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; self::SK_PREFIX . $id]),
            ],
        ]));

        return true;
    }

    public function gc(int $max_lifetime): int|false
    {
        // Rely on DynamoDB TTL to expire items; nothing to scan/delete here.
        return 0;
    }
}</code></pre>
<h4>Why it matters?</h4>
<p>This approach:</p>
<ul>
<li>Keeps your PHP sessions serverless-compatible.</li>
<li>Avoids cold-start pitfalls associated with local or in-memory session storage.</li>
<li>Provides a reliable, scalable, and fully managed solution for stateful data in a stateless environment.</li>
</ul>
<h3>CSRF Token Storage</h3>
<p>In a serverless environment, CSRF tokens must be handled carefully. Because Lambda executions are stateless, tokens cannot be stored in memory or on the filesystem. Instead, CSRF tokens are persisted in DynamoDB alongside session data.</p>
<p>This approach ensures tokens remain valid and verifiable across multiple Lambda invocations.</p>
<h4>How CSRF Tokens Are Stored</h4>
<p>Each CSRF token is stored as a dedicated item in the DynamoDB table:</p>
<ul>
<li>Tokens are associated with a specific action</li>
<li>Each token has a unique identifier</li>
<li>An expiration timestamp is stored for automatic cleanup</li>
</ul>
<p>This makes CSRF token storage consistent, durable, and serverless-compatible.</p>
<h4>Data Model</h4>
<p>CSRF tokens follow the same single-table design pattern used elsewhere in the application.</p>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>PK</td>
<td><code>CSRF</code></td>
</tr>
<tr>
<td>SK</td>
<td><code>TOKEN#&lt;token_id&gt;</code></td>
</tr>
<tr>
<td>session</td>
<td><code>&lt;session_id&gt;</code></td>
</tr>
<tr>
<td>expiresAt</td>
<td><code>&lt;timestamp&gt;</code></td>
</tr>
</tbody>
</table>
<p>Using a distinct partition key avoids contention and allows tokens to scale independently from session traffic.</p>
<h4>Lifecycle</h4>
<ol>
<li>A CSRF token is generated when a form is rendered.</li>
<li>The token is persisted in DynamoDB.</li>
<li>On form submission, the token is retrieved and validated.</li>
<li>After validation or expiration, the token is deleted or allowed to expire via TTL.</li>
</ol>
<p>This lifecycle mirrors traditional CSRF handling while remaining compatible with Lambda’s</p>
<h4>Implementation</h4>
<pre><code class="language-php">class DynamoDbCsrfTokenStorage implements CsrfTokenStorageInterface
{
    private const string PK_VALUE = &#039;CSRF&#039;;
    private const string SK_PREFIX = &#039;TOKEN#&#039;;

    public function getToken(string $tokenId): string
    {
        $result = $this-&gt;dynamoDb-&gt;getItem(new GetItemInput([
            &#039;TableName&#039; =&gt; $this-&gt;tableName,
            &#039;Key&#039; =&gt; [
                &#039;PK&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; self::PK_VALUE]),
                &#039;SK&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; self::SK_PREFIX . $tokenId]),
            ],
        ]));

        $item = $result-&gt;getItem();
        return $item[&#039;value&#039;]-&gt;getS() ?? &#039;&#039;;
    }

    public function setToken(string $tokenId, string $token): void
    {
        $expiresAt = time() + $this-&gt;ttlSeconds;

        $this-&gt;dynamoDb-&gt;putItem(new PutItemInput([
            &#039;TableName&#039; =&gt; $this-&gt;tableName,
            &#039;Item&#039; =&gt; [
                &#039;PK&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; self::PK_VALUE]),
                &#039;SK&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; self::SK_PREFIX . $tokenId]),
                &#039;value&#039; =&gt; new AttributeValue([&#039;S&#039; =&gt; $token]),
                &#039;expiresAt&#039; =&gt; new AttributeValue([&#039;N&#039; =&gt; (string) $expiresAt]),
            ],
        ]));
    }
}</code></pre>
<p>This ensures CSRF protection works seamlessly across multiple Lambda invocations.</p>
<h2>Symfony Configuration</h2>
<p>Configuring Symfony correctly is key for serverless PHP apps to work reliably with Lambda, DynamoDB, and Bref. Here’s how we set it up.</p>
<h3>1. Session Storage</h3>
<p>We replace the default PHP session handler with our <strong>DynamoDBSessionHandler</strong>:</p>
<pre><code class="language-yaml"># config/packages/framework.yaml
framework:
    session:
        handler_id: App\Session\DynamoDBSessionHandler
        cookie_secure: auto
        cookie_samesite: lax
        cookie_lifetime: 3600  # 1 hour</code></pre>
<p>Notes:</p>
<ul>
<li><code>handler_id</code> points to our custom service.</li>
<li><code>cookie_secure: auto</code> ensures HTTPS enforcement on Lambda URLs or custom domains.</li>
<li><code>cookie_lifetime</code> aligns with DynamoDB TTL for consistency.</li>
</ul>
<h3>2. Service definition</h3>
<p>Register the DynamoDB session handler as a Symfony service:</p>
<pre><code class="language-yaml"># config/services.yaml
services:
  App\Session\DynamoDbSessionHandler:
    arguments:
      $tableName: &#039;%book_table_name%&#039;
      $ttlSeconds: &#039;%env(default:session_ttl_seconds:int:SESSION_TTL)%&#039;</code></pre>
<ul>
<li><code>$tableName</code> comes from environment variables to support multiple environments.</li>
<li><code>$ttl</code> matches the session lifetime for automatic garbage collection.<br />
This configuration tells Symfony to use our custom handler for all session operations. The handler is automatically injected with the DynamoDB client through Symfony's autowiring.</li>
</ul>
<h3>3. RequestContextListener</h3>
<p>To handle dynamic Lambda Function URLs, we register a listener:</p>
<pre><code class="language-yaml"># config/services.yaml
services:
    App\EventListener\RequestContextListener:
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }</code></pre>
<p>Purpose:</p>
<ul>
<li>Ensures Symfony’s URL generator produces correct URLs.</li>
<li>Sets proper scheme and host for redirects, forms, and CSRF validation.</li>
<li>Essential for Lambda Function URLs where host/scheme changes per invocation.</li>
</ul>
<h4>Why It’s Needed</h4>
<p>Lambda Function URLs:</p>
<ul>
<li>Provide a direct HTTPS endpoint (e.g., <code>https://xyz.lambda-url.us-east-1.on.aws/</code>)</li>
<li>Are <strong>dynamic</strong> and unknown at build time</li>
<li>Require Symfony to know the <strong>scheme and host</strong> at runtime to generate correct URLs</li>
</ul>
<p>Without a listener:</p>
<ul>
<li>Redirects may point to HTTP instead of HTTPS</li>
<li>CSRF tokens may fail</li>
<li>Session cookies might be rejected</li>
<li>OAuth or SSO integrations could break</li>
</ul>
<h4>Implementation</h4>
<pre><code class="language-php">&lt;?php

namespace App\EventListener;

use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\RequestContext;

#[AsEventListener(event: KernelEvents::REQUEST, priority: 1024)]
class RequestContextListener
{
    public function __construct(private RequestContext $requestContext) {}

    public function onKernelRequest(RequestEvent $event): void
    {
        if (!$event-&gt;isMainRequest()) {
            return;
        }

        $request = $event-&gt;getRequest();

        // Force HTTPS for Lambda URLs and set proper context
        if ($request-&gt;headers-&gt;get(&#039;host&#039;) &amp;&amp; str_contains($request-&gt;headers-&gt;get(&#039;host&#039;), &#039;lambda-url&#039;)) {
            $this-&gt;requestContext-&gt;setScheme(&#039;https&#039;);
            $this-&gt;requestContext-&gt;setHost($request-&gt;headers-&gt;get(&#039;host&#039;));
            $this-&gt;requestContext-&gt;setHttpPort(80);
            $this-&gt;requestContext-&gt;setHttpsPort(443);

            $request-&gt;server-&gt;set(&#039;HTTPS&#039;, &#039;on&#039;);
            $request-&gt;server-&gt;set(&#039;SERVER_PORT&#039;, 443);
            $request-&gt;server-&gt;set(&#039;REQUEST_SCHEME&#039;, &#039;https&#039;);
        }
    }
}</code></pre>
<p><strong>The CDK Output Dilemma:</strong></p>
<pre><code class="language-typescript">// CDK can output the Lambda URL after deployment
new CfnOutput(this, &#039;LambdaURL&#039;, { 
    value: statelessStack.monolithLambdaFunctionUrl.url 
});
// But this value is only known AFTER deployment completes
// You can&#039;t use it as an environment variable in the SAME deployment</code></pre>
<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Tip: This listener is only necessary if you use the Lambda Function URL as the production endpoint. If you use a custom domain, this can be simplified or skipped.</p>
<p>With this configuration, Symfony becomes serverless-ready, maintaining sessions, CSRF protection, and routing behavior seamlessly while leveraging DynamoDB and Lambda.</p>
<h2>Single-Table Design Pattern</h2>
<p>All entities (sessions, CSRF tokens, users, books, authors) live in a <strong>single DynamoDB table</strong>. This simplifies the architecture and enables atomic operations across different entities.</p>
<table>
<thead>
<tr>
<th>Entity</th>
<th>PK</th>
<th>SK</th>
</tr>
</thead>
<tbody>
<tr>
<td>Session</td>
<td><code>SESSION</code></td>
<td><code>SID#&lt;session_id&gt;</code></td>
</tr>
<tr>
<td>CSRF Token</td>
<td><code>CSRF</code></td>
<td><code>TOKEN#&lt;token_id&gt;</code></td>
</tr>
<tr>
<td>Book</td>
<td><code>BOOK-METADATA</code></td>
<td><code>AUTHOR#&lt;author_id&gt;#BOOK#&lt;book_id&gt;</code></td>
</tr>
<tr>
<td>Author</td>
<td><code>AUTHOR-METADATA</code></td>
<td><code>AUTHOR#&lt;author_id&gt;</code></td>
</tr>
<tr>
<td>User</td>
<td><code>USER</code></td>
<td><code>EMAIL#&lt;email&gt;</code></td>
</tr>
</tbody>
</table>
<ul>
<li><strong>Why single-table?</strong>
<ul>
<li>Reduces infrastructure complexity.</li>
<li>Simplifies monitoring and backup.</li>
<li>Supports atomic transactions across multiple entity types.</li>
<li>Aligns with AWS best practices for DynamoDB.</li>
</ul>
</li>
</ul>
<h2>AWS CDK Infrastructure with Bref</h2>
<p>Deploying a serverless Symfony app requires some AWS setup. Using <strong>AWS CDK</strong> with <strong>Bref</strong> makes this smooth, maintainable, and repeatable.</p>
<h3>Why CDK?</h3>
<ul>
<li><strong>Infrastructure as code</strong>: Everything is versioned and reproducible.</li>
<li><strong>Integration with Symfony</strong>: Easy to link environment variables, DynamoDB, and Lambda functions.</li>
<li><strong>Bref-friendly</strong>: Deploy PHP Lambda layers without manually configuring Lambda functions.</li>
</ul>
<h3>Stateful Stack: DynamoDB Table</h3>
<pre><code class="language-ts">import { NestedStack } from &quot;aws-cdk-lib&quot;;
import * as ddb from &quot;aws-cdk-lib/aws-dynamodb&quot;;

export class BlogAppStatefulStack extends NestedStack {
  public readonly ddb: ddb.Table;

  constructor(scope: Construct, id: string, props: MyNestedStackProps) {
    super(scope, id, props);

    this.ddb = new ddb.Table(this, &#039;ddb&#039;, {
      tableName: `${id}-table`,
      partitionKey: { name: &#039;PK&#039;, type: ddb.AttributeType.STRING },
      sortKey: { name: &#039;SK&#039;, type: ddb.AttributeType.STRING },
      billingMode: ddb.BillingMode.PAY_PER_REQUEST,
      deletionProtection: props.shared.environment === &#039;prod&#039;,
      timeToLiveAttribute: &#039;expiresAt&#039;,
    });
  }
}</code></pre>
<p>Key features:</p>
<ul>
<li><strong>Generic Key Schema</strong>: <code>PK</code> and <code>SK</code> enable single-table design</li>
<li><strong>TTL Enabled</strong>: <code>expiresAt</code> attribute automatically removes expired items</li>
<li><strong>Production Protection</strong>: Deletion protection enabled for production environments</li>
</ul>
<h3>Stateless Stack: Lambda Function with Bref</h3>
<pre><code class="language-ts">import { packagePhpCode, PhpFpmFunction } from &quot;@bref.sh/constructs&quot;;
import * as lambda from &quot;aws-cdk-lib/aws-lambda&quot;;
import { FunctionUrl } from &quot;aws-cdk-lib/aws-lambda&quot;;

export class BlogAppStatelessStack extends NestedStack {
  public readonly monolithLambda: PhpFpmFunction;
  public monolithLambdaFunctionUrl: FunctionUrl;

  private createLambda(props: MyNestedStackProps, staticAssetsBucket: Bucket, ddb: ddb.Table) {
    const lambdaEnvironment = {
      APP_ENV: props.shared.environment,
      APP_SECRET: appSecret,
      ASSET_URL: `https://${staticAssetsBucket.bucketDomainName}/`,
      AWS_LAMBDA_LOG_FORMAT: &#039;text&#039;,
      BOOK_TABLE_NAME: `${props.shared.stackPrefix}-StatefulStack-table`,
    };

    const monolithLambda = new PhpFpmFunction(this, &#039;App&#039;, {
      handler: &#039;public/index.php&#039;,
      phpVersion: &#039;8.4&#039;,
      code: packagePhpCode(&#039;php&#039;, {
        exclude: [&#039;.env.local&#039;, &#039;bin/&#039;],
      }),
      functionName: `${props.shared.stackPrefix}-App`,
      timeout: Duration.seconds(28),
      memorySize: Size.gibibytes(2).toMebibytes(),
      environment: lambdaEnvironment,
    });

    // Create Function URL with no authentication
    const monolithLambdaFunctionUrl = monolithLambda.addFunctionUrl({ 
      authType: lambda.FunctionUrlAuthType.NONE 
    });

    // Grant DynamoDB permissions
    ddb.grantReadWriteData(monolithLambda);

    return { monolithLambda, monolithLambdaFunctionUrl };
  }
}</code></pre>
<h3>Lambda Function URL Configuration</h3>
<p>Lambda Function URLs provide a simple HTTPS endpoint without needing API Gateway:</p>
<pre><code class="language-ts">const monolithLambdaFunctionUrl = monolithLambda.addFunctionUrl({ 
  authType: lambda.FunctionUrlAuthType.NONE 
});</code></pre>
<p><strong>Benefits of Lambda URLs:</strong></p>
<ul>
<li><strong>Simplicity</strong>: Direct HTTPS endpoint without API Gateway complexity</li>
<li><strong>Cost</strong>: No API Gateway charges</li>
<li><strong>Performance</strong>: One less hop in the request path</li>
<li><strong>Built-in HTTPS</strong>: Automatic TLS certificate management</li>
</ul>
<p><strong>Configuration Options:</strong></p>
<ul>
<li><code>authType: NONE</code>: Public access (suitable for web applications)</li>
<li><code>authType: AWS_IAM</code>: Requires AWS signature (for service-to-service communication)</li>
</ul>
<h3>Main Stack: Orchestration</h3>
<pre><code class="language-ts">export class BlogApp extends Stack {
  constructor(scope: Construct, id: string, props: MyStackProps) {
    super(scope, id, props);

    const stackPrefix = props.shared.envStackPrefix;

    const statefulStack = new BlogAppStatefulStack(
      this, `${stackPrefix}-StatefulStack`, props
    );

    const statelessStack = new BlogAppStatelessStack(
      this, `${stackPrefix}-StatelessStack`, props, statefulStack
    );

    // Output important values
    new CfnOutput(this, &#039;Lambda&#039;, { 
      value: statelessStack.monolithLambda.functionName 
    });
    new CfnOutput(this, &#039;LambdaURL&#039;, { 
      value: statelessStack.monolithLambdaFunctionUrl.url 
    });
    new CfnOutput(this, &#039;DynamoDb&#039;, { 
      value: statefulStack.ddb.tableName 
    });
  }
}</code></pre>
<h3>Deployment with CDK</h3>
<p>With the infrastructure defined, deploying the application becomes a repeatable and predictable process. This section focuses on <strong>how the application is built, deployed, and updated</strong> using AWS CDK.</p>
<h4>Local Development Environment</h4>
<p>Local development mirrors the production setup as closely as possible while remaining lightweight.</p>
<ul>
<li>Docker is used to provide a consistent PHP environment.</li>
<li>A Makefile abstracts common commands to reduce cognitive load.</li>
<li>Symfony runs locally with the same session and configuration logic used in Lambda.</li>
</ul>
<p>You can run:</p>
<pre><code class="language-bash"># Pre-requisite - source your aws profile
make up</code></pre>
<p>You can check logs via <code>make logs</code>. And get into the container with <code>make bash</code>. The application will be available at <code>http://localhost:8000</code>, but it might fail to load as there is no existent DynamoDB to connect with. You can check local <code>.env</code> file for environment variables.</p>
<h4>Deploying</h4>
<p>Deploy the application using standard CDK commands (inside the container):</p>
<pre><code class="language-bash"># Pre-requisite - Bootstrap CDK if this is your first deployment - npx cdk bootstrap aws://&lt;ACCOUNT_ID&gt;/&lt;REGION&gt;
# Install dependencies
npm run deploy</code></pre>
<p>Alternatively, you can use the <code>Makefile</code> command outsite the container:</p>
<pre><code class="language-bash">make deploy</code></pre>
<h4>What Gets Created</h4>
<p>The deployment creates:</p>
<ol>
<li>DynamoDB table with TTL enabled</li>
<li>Lambda function with PHP 8.4 runtime (via Bref)</li>
<li>Lambda Function URL for HTTPS access</li>
<li>S3 bucket for static assets</li>
<li>IAM roles and permissions</li>
</ol>
<p>The output should be similar to:</p>
<pre><code class="language-bash">BlogApp (sandbox-blog-app): deploying... [1/1]
sandbox-blog-app: creating CloudFormation changeset...

 &#x2705;  BlogApp (sandbox-blog-app)

&#x2728;  Deployment time: 148.76s

Outputs:
BlogApp.AssetsBucket = sandbox-blog-app-sandboxbloga-assetsbucket5cb76180-5lu45xsqvuym
BlogApp.DynamoDb = sandbox-BlogApp-StatefulStack-table
BlogApp.Lambda = sandbox-BlogApp-App
BlogApp.LambdaURL = https://kiv7utcwku6gihqgs4bfkeuzma0oaylo.lambda-url.us-east-1.on.aws/
Stack ARN:
arn:aws:cloudformation:us-east-1:973974862728:stack/sandbox-blog-app/9ba92580-e50b-11f0-a602-0afffb8dc1a9

&#x2728;  Total time: 163.03s</code></pre>
<p>In this case, <code>https://kiv7utcwku6gihqgs4bfkeuzma0oaylo.lambda-url.us-east-1.on.aws/</code> is the Lambda public URL.</p>
<p>When you access the URL, you will see a log-in form. You can use the &quot;Register&quot; link to create a login. Use it and you will be able to manage Authors and Books. Try to log out and access the pages directly.</p>
<p><img data-recalc-dims="1" decoding="async" src="https://i0.wp.com/rafael.bernard-araujo.com/wp-content/uploads/2025/12/301225-1.png?w=580&#038;ssl=1" alt="Login" /></p>
<p><img data-recalc-dims="1" decoding="async" src="https://i0.wp.com/rafael.bernard-araujo.com/wp-content/uploads/2025/12/301225-2.png?w=580&#038;ssl=1" alt="Register" /></p>
<p><img data-recalc-dims="1" decoding="async" src="https://i0.wp.com/rafael.bernard-araujo.com/wp-content/uploads/2025/12/301225-3.png?w=580&#038;ssl=1" alt="Main" /></p>
<p>Internally it will execute a series of commands:</p>
<pre><code class="language-bash"># clean
npm run clean &amp;&amp; \
# execute php packaging including composer install and npm build for symfony
npm run package:sandbox &amp;&amp; \ 
# deploy as a sandbox not requiring approval
NODE_ENV=sandbox cdk deploy --require-approval never</code></pre>
<p>There is a prod version executing <code>make deploy:prod</code>.</p>
<h3>Testing the Session Implementation</h3>
<p>The application includes a test endpoint to verify session persistence:</p>
<pre><code class="language-php">#[Route(&#039;/session-test&#039;, name: &#039;session_test&#039;)]
public function test(Request $request): JsonResponse
{
    $session = $request-&gt;getSession();
    $counter = $session-&gt;get(&#039;counter&#039;, 0);
    $session-&gt;set(&#039;counter&#039;, $counter + 1);

    return new JsonResponse([
        &#039;message&#039; =&gt; &#039;Session test&#039;,
        &#039;session_id&#039; =&gt; $session-&gt;getId(),
        &#039;counter&#039; =&gt; $session-&gt;get(&#039;counter&#039;),
        &#039;handler&#039; =&gt; get_class($session-&gt;getMetadataBag()-&gt;getMetadata(&#039;handler&#039;)),
    ]);
}</code></pre>
<p>Test with curl:</p>
<pre><code class="language-bash"># First request creates session
curl -i -c cookie.txt https://your-lambda-url/session-test

# Subsequent requests increment counter
curl -i -b cookie.txt https://your-lambda-url/session-test
curl -i -b cookie.txt https://your-lambda-url/session-test

# outputs
➜ curl -i -c cookie.txt https://your-lambda-url/session-test
{&quot;message&quot;:&quot;Session incremented&quot;,&quot;session_id&quot;:&quot;02b0c08e1ccd5f3ea015a06c69e29d11&quot;,&quot;counter&quot;:1,&quot;handler&quot;:&quot;App\\Session\\DynamoDbSessionHandler&quot;}%

➜ curl -i -b cookie.txt https://your-lambda-url/session-test
{&quot;message&quot;:&quot;Session incremented&quot;,&quot;session_id&quot;:&quot;02b0c08e1ccd5f3ea015a06c69e29d11&quot;,&quot;counter&quot;:2,&quot;handler&quot;:&quot;App\\Session\\DynamoDbSessionHandler&quot;}%

➜ curl -i -b cookie.txt https://your-lambda-url/session-test
{&quot;message&quot;:&quot;Session incremented&quot;,&quot;session_id&quot;:&quot;02b0c08e1ccd5f3ea015a06c69e29d11&quot;,&quot;counter&quot;:3,&quot;handler&quot;:&quot;App\\Session\\DynamoDbSessionHandler&quot;}%</code></pre>
<h2>Performance Considerations</h2>
<h3>Cold Start Optimization</h3>
<ol>
<li><strong>Memory Allocation</strong>: Using 2GB memory reduces cold start times</li>
<li><strong>Composer Optimization</strong>: <code>--no-dev --optimize-autoloader</code> reduces code size</li>
<li><strong>PHP 8.4</strong>: Latest PHP version with JIT compiler support</li>
</ol>
<h3>DynamoDB Performance</h3>
<ol>
<li><strong>Consistent Reads</strong>: Ensures session consistency at the cost of slightly higher latency</li>
<li><strong>On-Demand Billing</strong>: No capacity planning, automatic scaling</li>
<li><strong>TTL</strong>: Automatic cleanup without scan operations</li>
</ol>
<p>The serverless model's primary advantage is alignment of costs with actual usage, particularly beneficial for applications with variable or unpredictable traffic patterns. However, actual costs vary significantly based on traffic patterns, request complexity, and specific use cases. It's recommended to use AWS cost estimation tools and monitor actual usage to understand the financial impact for your specific application.</p>
<h2>Security Best Practices</h2>
<h3>Session Security</h3>
<ol>
<li><strong>Secure Flag</strong>: Ensures cookies only sent over HTTPS</li>
<li><strong>SameSite</strong>: Protects against CSRF attacks</li>
<li><strong>Regenerate ID</strong>: After authentication to prevent session fixation</li>
</ol>
<pre><code class="language-yaml">framework:
    session:
        cookie_httponly: true
        cookie_secure: auto
        cookie_samesite: lax</code></pre>
<h3>DynamoDB Permissions</h3>
<p>The Lambda function requires minimal permissions:</p>
<pre><code class="language-typescript">ddb.grantReadWriteData(monolithLambda);</code></pre>
<p>This grants only:</p>
<ul>
<li><code>dynamodb:GetItem</code></li>
<li><code>dynamodb:PutItem</code></li>
<li><code>dynamodb:DeleteItem</code></li>
<li><code>dynamodb:Query</code></li>
<li><code>dynamodb:Scan</code></li>
</ul>
<p>No administrative permissions are granted to the Lambda function.</p>
<h2>Limitations</h2>
<p>While serverless PHP with DynamoDB sessions offers compelling advantages, it's important to understand the limitations and trade-offs. Here's an honest assessment of where this architecture may not be the best fit:</p>
<h3>1. Cold Start Latency</h3>
<p><strong>The Reality</strong>: Lambda cold starts can add <strong>1-3 seconds</strong> to the first request after a function has been idle. In practice this occurs for less than 1% of the calls.</p>
<p><strong>Mitigation Strategies</strong>:</p>
<ul>
<li><strong>Provisioned Concurrency</strong>: Pre-warm Lambda instances to eliminate cold starts (adds ~$15/month per instance)</li>
<li><strong>Keep-Warm Pings</strong>: Use CloudWatch Events to invoke functions every 5-10 minutes (adds minimal cost but doesn't help with scaling)</li>
<li><strong>Larger Memory Allocation</strong>: We use 2GB memory which provides faster CPUs, reducing cold start duration</li>
<li><strong>Optimize Code</strong>: Minimize dependencies, use PHP preloading, optimize autoloader</li>
</ul>
<p><strong>When it's acceptable</strong>: Background jobs, internal tools, APIs with relaxed SLAs<br />
<strong>When it's problematic</strong>: User-facing e-commerce, real-time chat, gaming applications</p>
<h3>2. Request Timeout Constraints</h3>
<p><strong>The Reality</strong>: Our configuration uses <strong>28 seconds timeout</strong> (API Gateway compatible), though Lambda supports up to <strong>15 minutes</strong>, which Lambda URLs supports.</p>
<p><strong>Not Suitable For</strong>:</p>
<ul>
<li><strong>Long-running batch jobs</strong>: Data exports, report generation, video processing</li>
<li><strong>Large file uploads</strong>: Direct file uploads over 10MB become unreliable</li>
<li><strong>Complex data migrations</strong>: Multi-step transformations requiring minutes to complete</li>
<li><strong>WebSocket connections</strong>: Not supported by Lambda Function URLs (use API Gateway WebSocket instead)</li>
</ul>
<p><strong>Recommended Alternatives</strong>:</p>
<ul>
<li><strong>Keep Lambda URL</strong>: If API Gateway specific features are not needed, we can use custom domain with Lambda URLs and process up to 15 minutes</li>
<li><strong>AWS Step Functions</strong>: Orchestrate long-running workflows across multiple Lambda invocations</li>
<li><strong>ECS/Fargate</strong>: For truly long-running processes (hours), use containers instead</li>
<li><strong>Presigned S3 URLs</strong>: For large file uploads, let clients upload directly to S3</li>
<li><strong>SQS + Background Workers</strong>: Offload heavy processing to asynchronous queues</li>
</ul>
<h3>3. Session Consistency Edge Cases</h3>
<p><strong>The Reality</strong>: DynamoDB is eventually consistent by default, but we use <code>ConsistentRead: true</code> to mitigate this.</p>
<p><strong>Why We Use ConsistentRead</strong>:</p>
<pre><code class="language-php">&#039;ConsistentRead&#039; =&gt; true,  // Ensures we always get the latest session data</code></pre>
<p><strong>Rare Race Conditions</strong>:<br />
Even with consistent reads, race conditions can occur when:</p>
<ul>
<li><strong>Simultaneous Writes</strong>: User opens multiple tabs, both modify session simultaneously—last write wins</li>
<li><strong>Write-then-Read Timing</strong>: Session written in one Lambda, immediately read by another—minimal delay possible</li>
<li><strong>Cross-Region Scenarios</strong>: If using Global Tables, replication lag can cause stale reads in remote regions</li>
</ul>
<p><strong>Practical Impact</strong>: In 99.9% of cases, consistent reads solve the problem. Edge cases typically affect power users opening many tabs or distributed teams across continents.</p>
<p><strong>Mitigation</strong>: For critical operations (e.g., payment processing), use DynamoDB conditional expressions to ensure atomic updates and detect conflicts.</p>
<h3>4. DynamoDB Costs at Scale</h3>
<p>DynamoDB's pay-per-request pricing is cost-effective at low-to-moderate traffic but pricing characteristics change at high scale.</p>
<h4>Assumptions</h4>
<p><strong>DynamoDB</strong>:</p>
<ul>
<li>On-demand billing: $1.25 per million reads/writes</li>
<li>1KB session item size</li>
<li>1 read + 1 write per request</li>
</ul>
<p><strong>Redis (ElastiCache)</strong>:</p>
<ul>
<li>t4g.medium: $0.037/hr (~$27/month)</li>
<li>1 node sufficient for low-medium traffic</li>
<li>High traffic may require bigger node(s)</li>
</ul>
<h4>Cost Table</h4>
<table>
<thead>
<tr>
<th>Traffic</th>
<th>Requests / Month</th>
<th>DynamoDB Cost</th>
<th>Redis Cost</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Low</td>
<td>1M</td>
<td>$2.50</td>
<td>$27</td>
<td>DynamoDB far cheaper at low traffic</td>
</tr>
<tr>
<td>Medium</td>
<td>10M</td>
<td>$25</td>
<td>$27</td>
<td>Costs roughly similar; DynamoDB slightly lower ops</td>
</tr>
<tr>
<td>High</td>
<td>50M</td>
<td>$125</td>
<td>$108 (cache.m5.large 3 nodes)</td>
<td>Redis may become cheaper with large, sustained traffic, but ops complexity rises</td>
</tr>
</tbody>
</table>
<p><strong>When Fixed Infrastructure (like Redis/ElastiCache) May Become More Cost-Effective</strong>:</p>
<ul>
<li>Sustained high traffic volumes where fixed costs are fully utilized</li>
<li>Long-lived sessions with more reads than writes</li>
<li>Advanced caching features needed beyond simple session storage</li>
</ul>
<p><strong>Hidden DynamoDB Cost Factors</strong>:</p>
<ul>
<li>Consistent reads cost more than eventually consistent reads</li>
<li>Session writes on every request (even if session data unchanged)</li>
<li>AWS free tier limitations after 12 months</li>
</ul>
<p>Start with DynamoDB for simplicity and operational efficiency. Monitor costs monthly as traffic grows. If costs become a concern at high scale, evaluate whether fixed infrastructure or caching optimizations make sense for your specific use case.</p>
<hr />
<p>These limitations are not dealbreakers but they're <strong>trade-offs</strong>. For the right use cases (bursty traffic, cost-sensitive, minimal ops), the benefits far outweigh the drawbacks.</p>
<h2>Conclusion</h2>
<p>Building serverless PHP applications doesn't require sacrificing familiar frameworks or patterns. By implementing a custom DynamoDB session handler, we achieve:</p>
<ul>
<li><strong>Truly serverless architecture</strong>: No Redis, no EFS, pure AWS managed services</li>
<li><strong>Production-ready session management</strong>: Consistent, scalable, and secure</li>
<li><strong>Cost-effective</strong>: Pay only for actual usage</li>
<li><strong>Developer-friendly</strong>: Standard Symfony application with minimal modifications</li>
<li><strong>Type-safe infrastructure</strong>: AWS CDK with TypeScript</li>
<li><strong>Modern PHP</strong>: PHP 8.4 with all latest features</li>
<li><strong>Local development</strong>: Docker-compose for local testing</li>
</ul>
<p>The combination of Bref for Lambda PHP support, Symfony for application framework, and DynamoDB for stateful storage creates a robust, scalable, and maintainable serverless application architecture.</p>
<h3>When Should You Use This Architecture?</h3>
<p><strong>Choose this approach when:</strong></p>
<ul>
<li>Traffic is unpredictable or bursty (blogs, seasonal apps, internal tools)</li>
<li>Cost optimization matters more than absolute performance</li>
<li>Zero operational overhead is a priority</li>
<li>You need automatic scaling without capacity planning</li>
</ul>
<p>Common use cases are:</p>
<ul>
<li>CMS - Blogs, documentation sites, and knowledge bases with infrequent or sporadic traffic, when sudden spikes are scaled automatically and quite periods costs pennies</li>
<li>Admin Panels and Internal Tools - Dashboard interfaces, internal reporting tools, and back-office applications with sporadic usage patterns. DynamoDB maintains session state without requiring Redis or similar infrastructure.</li>
<li>Multi-Tenant SaaS Applications - B2B platforms where each tenant has independent traffic patterns. DynamoDB's single-table design efficiently manages sessions across all tenants without cross-tenant interference.</li>
<li>API Services with Session Requirements - REST APIs that need stateful operations like OAuth flows, multi-step workflows, or temporary data caching. No Redis clusters to maintain, no session cleanup cron jobs to manage. DynamoDB TTL handles everything automatically.</li>
<li>Seasonal Applications - Event registration systems, holiday campaign sites, tax filing applications, and other time-bound services.</li>
<li>Microservices Requiring Session State - Distributed systems where individual services need temporary state management across invocations.</li>
</ul>
<p><strong>Consider alternatives when:</strong></p>
<ul>
<li>You require consistent sub-100ms response times</li>
<li>Traffic is predictable and sustained at high levels (&gt;10M requests/month)</li>
<li>Long-running processes or WebSocket connections are needed</li>
</ul>
<h3>The Bigger Picture</h3>
<p>This implementation demonstrates that <strong>serverless and stateful aren't mutually exclusive</strong>. While serverless advocates often emphasize &quot;stateless functions,&quot; real-world applications need state management. The key is choosing the right state storage mechanism, and DynamoDB proves that managed, serverless databases can handle session management as effectively as traditional infrastructure, with far less operational burden.</p>
<p>Whether you're building a content management system, an internal admin panel, or a multi-tenant SaaS application, this architecture provides a production-ready foundation. Start simple, monitor costs and performance, and scale confidently knowing your infrastructure will grow with your application without requiring a dedicated ops team.</p>
<h2>Resources</h2>
<ul>
<li><a href="https://bref.sh/">Bref Documentation</a></li>
<li><a href="https://github.com/brefphp/constructs">Bref CDK Constructs</a></li>
<li><a href="https://async-aws.com/clients/dynamodb.html">AsyncAws DynamoDB Client</a></li>
<li><a href="https://symfony.com/doc/current/session.html">Symfony Session Documentation</a></li>
<li><a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html">Lambda Function URLs</a></li>
<li><a href="https://www.alexdebrie.com/posts/dynamodb-single-table/">DynamoDB Single-Table Design</a></li>
</ul>
<h2>Source Code</h2>
<p>The complete source code for this application is available at: <a href="https://github.com/rafaelbernard/serverless-php-with-bref-symfony-and-dynamodb-session-management/">rafaelbernard/serverless-php-with-bref-symfony-and-dynamodb-session-management/</a></p>
<p>For detailed technical implementation notes, test coverage reports, and deployment validation, see <a href="https://github.com/rafaelbernard/serverless-php-with-bref-symfony-and-dynamodb-session-management/blob/master/IMPLEMENTATION_SUMMARY.md"><code>IMPLEMENTATION_SUMMARY.md</code></a> in the repository. This document covers:</p>
<ul>
<li>Complete test suite (112 tests across PHP and CDK)</li>
<li>Infrastructure validation details</li>
<li>Code quality metrics</li>
<li>Deployment procedures and best practices</li>
</ul>
<h3><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Bonus: Guide to Custom Domain Configuration with Route53</h3>
<p>Lambda Function URLs provide a quick way to expose your Lambda function over HTTPS, but the auto-generated URL (e.g., <code>https://abc123xyz.lambda-url.us-east-1.on.aws/</code>) isn't branded or memorable. But you can add a Simple CNAME Mapping: Direct Route53 CNAME to Lambda Function URL (easiest, limited SSL control).</p>
<p>This is the <strong>quickest and easiest</strong> method. Just create a CNAME record pointing to your Lambda Function URL. Best for internal tools, prototypes, and non-production environments.</p>
<h4>Prerequisites</h4>
<p>Before configuring custom domains, ensure you have:</p>
<ol>
<li><strong>Domain registered in Route53</strong> (or another registrar with ability to update nameservers)</li>
<li><strong>Hosted Zone created in Route53</strong> for your domain</li>
</ol>
<h4>Implementation with CDK</h4>
<p>Here's how to add a custom domain CNAME record pointing to your Lambda Function URL using AWS CDK:</p>
<pre><code class="language-typescript">import * as route53 from &#039;aws-cdk-lib/aws-route53&#039;;
import * as route53Targets from &#039;aws-cdk-lib/aws-route53-targets&#039;;
import * as lambda from &#039;aws-cdk-lib/aws-lambda&#039;;
import { Construct } from &#039;constructs&#039;;

// Assuming you have a Lambda function with Function URL enabled
const myFunction = new lambda.Function(this, &#039;MyFunction&#039;, {
  // ... function configuration
  functionUrlOptions: {
    authType: lambda.FunctionUrlAuthType.NONE, // or AWS_IAM
  },
});

// Get the hosted zone for your domain
const hostedZone = route53.HostedZone.fromLookup(this, &#039;HostedZone&#039;, {
  domainName: &#039;yourdomain.com&#039;,
});

// Create CNAME record pointing to Lambda URL
new route53.CnameRecord(this, &#039;LambdaUrlCname&#039;, {
  zone: hostedZone,
  recordName: &#039;api&#039;, // Creates api.yourdomain.com
  domainName: cdk.Fn.parseDomainName(myFunction.functionUrl), // Extracts hostname from URL
  ttl: cdk.Duration.minutes(5),
  comment: &#039;CNAME to Lambda Function URL&#039;,
});

// Output the custom domain
new cdk.CfnOutput(this, &#039;CustomDomainUrl&#039;, {
  value: `https://api.yourdomain.com`,
  description: &#039;Custom domain URL for Lambda function&#039;,
});</code></pre>
<h4>Testing Your CNAME Setup</h4>
<p>After creating the CNAME record, verify it works:</p>
<pre><code class="language-bash"># Check DNS propagation
dig api.yourdomain.com

# Test the endpoint
curl -i https://api.yourdomain.com/

# Verify SSL certificate
openssl s_client -connect api.yourdomain.com:443 -servername api.yourdomain.com | grep subject</code></pre>
<p><strong>Expected Results</strong>:</p>
<ul>
<li>DNS query returns Lambda Function URL hostname as CNAME target</li>
<li>HTTP request succeeds with same response as Lambda URL</li>
<li>SSL certificate shows AWS-managed certificate (not your custom domain)</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://rafael.bernard-araujo.com/building-a-serverless-php-application-with-bref-symfony-and-dynamodb-session-management.php/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2283</post-id>	</item>
		<item>
		<title>Tropeçando 117</title>
		<link>https://rafael.bernard-araujo.com/tropecando-117.php</link>
					<comments>https://rafael.bernard-araujo.com/tropecando-117.php#respond</comments>
		
		<dc:creator><![CDATA[rafael]]></dc:creator>
		<pubDate>Thu, 20 Nov 2025 08:02:00 +0000</pubDate>
				<category><![CDATA[Tropeçando]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[llm]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[site reliability]]></category>
		<category><![CDATA[software engineering]]></category>
		<guid isPermaLink="false">https://rafael.bernard-araujo.com/?p=2221</guid>

					<description><![CDATA[How far can we push AI autonomy in code generation? We ran a series of experiments to explore how far Generative AI can currently be pushed toward autonomously developing high-quality, up-to-date software without human intervention. As a test case, we created an agentic workflow to build a simple Spring Boot application end to end. We [&#8230;]]]></description>
										<content:encoded><![CDATA[<p><a href="https://martinfowler.com/articles/pushing-ai-autonomy.html">How far can we push AI autonomy in code generation?</a></p>
<blockquote>
<p>We ran a series of experiments to explore how far Generative AI can currently be pushed toward autonomously developing high-quality, up-to-date software without human intervention. As a test case, we created an agentic workflow to build a simple Spring Boot application end to end. We found that the workflow could ultimately generate these simple applications, but still observed significant issues in the results—especially as we increased the complexity. The model would generate features we hadn't asked for, make shifting assumptions around gaps in the requirements, and declare success even when tests were failing. We concluded that while many of our strategies — such as reusable prompts or a reference application — are valuable for enhancing AI-assisted workflows, a human in the loop to supervise generation remains essential. </p>
</blockquote>
<p><a href="https://thephp.foundation/blog/2025/09/05/php-mcp-sdk/">Announcing the Official PHP SDK for MCP</a></p>
<blockquote>
<p>The PHP Foundation, Anthropic’s MCP team, and Symfony are collaborating on the official PHP SDK for the Model Context Protocol (MCP). Our goal is a framework-agnostic, production-ready reference implementation the PHP ecosystem can rely on.</p>
</blockquote>
<p><a href="https://ashallendesign.co.uk/blog/covariance-and-contravariance-in-php">Covariance and Contravariance in PHP </a></p>
<blockquote>
<p>Before we dive into the details and code examples, let me quickly define covariance and contravariance:</p>
<p>Covariance: Making something more specific<br />
Contravariance: Making something less specific</p>
<p>Now let's dive in and see how these concepts apply to PHP.</p>
</blockquote>
<p><a href="https://slack.engineering/break-stuff-on-purpose/">Break Stuff on Purpose</a></p>
<blockquote>
<p>Strengthen your system’s ability to recover by intentionally causing and resolving failures</p>
</blockquote>
<p><a href="https://read.thecoder.cafe/p/nothing-beats-kindness">Nothing Beats Kindness</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://rafael.bernard-araujo.com/tropecando-117.php/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2221</post-id>	</item>
		<item>
		<title>Principles in Refactoring &#8211; Slowing Down New Features?</title>
		<link>https://rafael.bernard-araujo.com/principles-in-refactoring-slowing-down-new-features.php</link>
					<comments>https://rafael.bernard-araujo.com/principles-in-refactoring-slowing-down-new-features.php#respond</comments>
		
		<dc:creator><![CDATA[rafael]]></dc:creator>
		<pubDate>Wed, 22 Oct 2025 02:59:39 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[software engineering]]></category>
		<guid isPermaLink="false">https://rafael.bernard-araujo.com/?p=2252</guid>

					<description><![CDATA[The whole purpose of refactoring is to make us program faster, producing more value with less effort. and But I think the most dangerous way that people get trapped is when they try to justify refactoring in terms of &#34;clean code&#34;, &#34;good engineering practice&#34;, or similar moral reasons. The point of refactoring isn't to show [&#8230;]]]></description>
										<content:encoded><![CDATA[<blockquote>
<p>The whole purpose of refactoring is to make us program faster, producing more value with less effort.</p>
</blockquote>
<p>and</p>
<blockquote>
<p>But I think the most dangerous way that people get trapped is when they try to justify refactoring in terms of &quot;clean code&quot;, &quot;good engineering practice&quot;, or similar moral reasons. The point of refactoring isn't to show how sparkly a code base is -- it is purely economic. We refactor because it makes us faster -- fastor add features, faster to fix bugs.</p>
</blockquote>
<p>-- From <em>Refactoring: Improving the Design of Existing Code</em> (Martin Fowler and Kent Beck), page 56</p>
]]></content:encoded>
					
					<wfw:commentRss>https://rafael.bernard-araujo.com/principles-in-refactoring-slowing-down-new-features.php/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2252</post-id>	</item>
	</channel>
</rss>
