<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title><![CDATA[sph.sh Blog]]></title>
        <description><![CDATA[Ayhan Sipahi shares practical software engineering insights, architecture notes, and technical writing from Berlin.]]></description>
        <link>https://sph.sh</link>
        <generator>RSS for Node</generator>
        <lastBuildDate>Fri, 14 Aug 2026 13:52:58 GMT</lastBuildDate>
        <atom:link href="https://sph.sh/rss/en.xml" rel="self" type="application/rss+xml"/>
        <pubDate>Fri, 14 Aug 2026 13:52:58 GMT</pubDate>
        <copyright><![CDATA[2026 sph.sh]]></copyright>
        <language><![CDATA[en]]></language>
        <ttl>60</ttl>
        <item>
            <title><![CDATA[Dogwood: Temporal Authorization for AI Agents]]></title>
            <description><![CDATA[How AWS Dogwood adds temporal conditions to Cedar policies, lowers them back to plain Cedar, and enforces agent guardrails at the Amazon Bedrock AgentCore gateway.]]></description>
            <link>https://sph.sh/en/posts/dogwood-temporal-policy-language/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/dogwood-temporal-policy-language/</guid>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[ai-agents]]></category>
            <category><![CDATA[aws-bedrock]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Point-in-time authorization answers one question: is this single request allowed? For an autonomous agent that calls tools in sequence, that is necessary but not sufficient. A tool call can be valid on its own and still wrong given what the agent did just before. AWS open-sourced Dogwood on August 6, 2026 under Apache 2.0 to close that gap. Dogwood is a policy language that adds temporal conditions on top of Cedar, then compiles ("lowers") each temporal clause back to ordinary Cedar. A working knowledge of Cedar is assumed here. The focus is what Dogwood adds on top: the temporal operators, how lowering works, how Amazon Bedrock AgentCore enforces temporal policies at the gateway, and where the honest limits sit. For a refresher on Cedar itself, see the Cedar vs Rego vs OpenFGA comparis...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Where Should Claude Code Run? Devcontainer, Codespaces, or MicroVM]]></title>
            <description><![CDATA[Devcontainers, Codespaces and AWS Lambda MicroVMs as homes for a coding agent: what each rung adds, what it costs, and when moving the agent off the laptop pays off.]]></description>
            <link>https://sph.sh/en/posts/claude-code-lambda-microvm-dev-sandboxes/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/claude-code-lambda-microvm-dev-sandboxes/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[ai-coding]]></category>
            <category><![CDATA[aws-bedrock]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[developer-workflow]]></category>
            <category><![CDATA[serverless]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 04 Aug 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Prisma vs Drizzle vs Kysely: Choosing a TypeScript Data Layer]]></title>
            <description><![CDATA[A decision framework for the TypeScript data layer on Postgres: how much SQL the library should own, what it costs on Lambda, and when the default is wrong.]]></description>
            <link>https://sph.sh/en/posts/prisma-vs-drizzle-vs-kysely/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/prisma-vs-drizzle-vs-kysely/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[prisma]]></category>
            <category><![CDATA[drizzle]]></category>
            <category><![CDATA[kysely]]></category>
            <category><![CDATA[orm]]></category>
            <category><![CDATA[serverless]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 03 Aug 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Every new TypeScript service on Postgres forces a decision that is expensive to reverse: how much of the SQL does a library get to own? Prisma, Drizzle, and Kysely answer differently, and the difference is not a feature list. It is about where the types come from and who writes the query. For a new Postgres-backed service on Lambda, the default is Drizzle: it owns the schema and the migrations while still leaving the query shape visible in the code. Two conditions override that default. The serverless objection to Prisma that most comparisons still repeat has expired as well, and the narrower objection that replaced it is easier to miss.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Slow Down to Speed Up]]></title>
            <description><![CDATA[Rushing feels fast but creates rework, bugs, and firefighting. Why pausing for refactoring, tests, and CI upkeep is an investment in speed, not lost speed.]]></description>
            <link>https://sph.sh/en/posts/slow-down-to-speed-up-sustainable-engineering-pace/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/slow-down-to-speed-up-sustainable-engineering-pace/</guid>
            <category><![CDATA[technical-debt]]></category>
            <category><![CDATA[refactoring]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[dora-metrics]]></category>
            <category><![CDATA[engineering-culture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Skipping the cleanup to hit a date feels fast in the moment, and over a single sprint it often is. The problem is that skipping the test, the small refactor, or the flaky-pipeline fix does not delete that work. It defers the work at interest, and the interest comes back as bugs, rework, and time lost to firefighting. The intuitive read is that maintenance is time not spent shipping, so it must slow you down. Research on delivery performance points the other way: teams that keep their code and pipeline clean tend to ship more often. The recommendation here is to default to a steady, maintenance-inclusive pace, and to treat technical debt as a deliberate, scheduled decision rather than an accident.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Who Owns This Code Now: Accountability vs Blame]]></title>
            <description><![CDATA[Stop asking who wrote the legacy code. Separate responsibility, accountability, and blame, and make inherited code owned rather than orphaned.]]></description>
            <link>https://sph.sh/en/posts/code-ownership-accountability-vs-blame/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/code-ownership-accountability-vs-blame/</guid>
            <category><![CDATA[engineering-culture]]></category>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[psychological-safety]]></category>
            <category><![CDATA[technical-debt]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>"Whoever wrote this was an idiot" is one of the most common sentences in software, and one of the least useful. It feels like analysis; it answers a question that does not matter. The moment you open a file and change one line, you own its behavior in production, no matter who typed the original code. Authorship is history. Ownership is present tense. For most engineering teams, the durable answer is team-level service ownership backed by a machine-readable registry, a CODEOWNERS file plus a service catalog, and a real on-call rotation. What makes it work is three words teams wrongly treat as synonyms: responsibility, accountability, and blame.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Thinking in Events: A Beginner's Guide to Commands vs Events]]></title>
            <description><![CDATA[Learn the mental shift behind event-driven systems: announce facts instead of issuing commands. Covers naming, decoupling, eventual consistency, and idempotency.]]></description>
            <link>https://sph.sh/en/posts/thinking-in-events-beginner-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/thinking-in-events-beginner-guide/</guid>
            <category><![CDATA[event-driven-architecture]]></category>
            <category><![CDATA[messaging]]></category>
            <category><![CDATA[idempotency]]></category>
            <category><![CDATA[eventual-consistency]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 17 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Many teams adopt a message broker, rename their RPC calls to "events," and keep thinking in commands. The rename changes the label, not the mindset. A command still says "do this now," addressed to one known worker, so nothing is actually decoupled. You pay the operational cost of messaging and get none of the benefit.</p>
<p>If you are comfortable with request/response HTTP and CRUD but have never built an event-driven system, one small shift makes it click. Announce facts that already happened (events) instead of issuing instructions (commands). Because that shift is a way of thinking, you can get it right before you touch any broker.</p>
<p><strong>Command, Event, or Message</strong></p>
<p>Three words get used as if they mean the same thing. They do not.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Choosing a Serverless Internal Service Layer]]></title>
            <description><![CDATA[Before building an internal service layer, decide whether you need one: what it costs per call, the volume where VPC Lattice wins, and when direct invoke still beats it.]]></description>
            <link>https://sph.sh/en/posts/serverless-internal-service-layer-101-choosing-the-layer/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-internal-service-layer-101-choosing-the-layer/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[vpc]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>When a serverless estate grows past one team, services start calling each other. The usual answer is a cross-team grant of lambda:InvokeFunction on someone else's function ARN. That grant turns a function name into a public contract. The callee can no longer rename, split, or replace the function without breaking a consumer that lives in a repository they cannot see. The naive fix, wrapping the callee in an SDK client library, does not help: the coupling lives in the IAM policy, where no client library can reach it.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[gRPC, Protobuf, and the Wire Format Your Gateway Allows]]></title>
            <description><![CDATA[A private REST API structurally cannot carry gRPC, and every AWS surface that speaks gRPC excludes Lambda targets. What to keep from gRPC, and what to drop.]]></description>
            <link>https://sph.sh/en/posts/serverless-internal-service-layer-102-protocols-payloads/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-internal-service-layer-102-protocols-payloads/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[vpc]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>You have chosen this layer over the alternatives. The next question is what it can actually carry, and anyone arriving from the service-mesh world asks it within a minute of seeing the layer from part 101: why not gRPC? It is the default east-west transport there, and the question is fair.</p>
<p>The answer is structural. A private REST API enforces requests down to HTTP/1.1, and gRPC is carried over HTTP/2 framing. Worse, every AWS surface that does speak gRPC excludes Lambda functions as targets, so switching gateways does not rescue you. But the thing teams actually want from gRPC survives all of this, because it was never the transport. It was the schema.</p>
<p><strong>Why gRPC is not on the table</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building the Layer with a Private API Gateway]]></title>
            <description><![CDATA[The private REST API, the resource policy that switches it on, per-route AWS_IAM grants, the two CDK stacks, and signing the call from a Node 22 Lambda.]]></description>
            <link>https://sph.sh/en/posts/serverless-internal-service-layer-103-building-the-layer/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-internal-service-layer-103-building-the-layer/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[vpc]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>You have chosen the layer (part 101) and settled what it carries (part 102). This part is the build: the private REST API, the resource policy that makes it reachable, AWS_IAM grants scoped per route, the two CDK stacks split on the ownership boundary, the SigV4 call from a Node 22 Lambda, and the operational facts about VPC-attached callers that decide whether any of it works. The substitution at its heart stays small: the caller's execution role holds execute-api:Invoke on a route ARN instead of lambda:InvokeFunction on a function ARN, and the callee's function ARN never leaves the callee's stack. The private endpoint constraint comes first, because it settles the API type before anything else.</p>
<p><strong>Private endpoints are REST-only</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Identity and Encryption Between Serverless Services]]></title>
            <description><![CDATA[SigV4 proves which service is calling and nothing about which user it is for. How to propagate a verified subject, and what the transport actually encrypts.]]></description>
            <link>https://sph.sh/en/posts/serverless-internal-service-layer-104-identity-encryption/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-internal-service-layer-104-identity-encryption/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[vpc]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Part 103 built the layer: a private REST API reached over a shared execute-api interface endpoint, with every call signed with SigV4 and authorized per route. That layer answers one question well and leaves a second one wide open.</p>
<p>SigV4 proves <strong>which service</strong> is calling. It proves nothing about <strong>which user</strong> the call is for. Those are different questions, and a layer that only answers the first is a layer where any service can act as any user. This part closes that gap, then states precisely what the transport does and does not encrypt.</p>
<p><strong>Propagating a verified subject</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Multi-Account: Cross-Account Calls and the Data Perimeter]]></title>
            <description><![CDATA[Same-account, the resource policy and the caller's identity policy are an OR. Cross-account they become an AND, and silence denies. What that changes in the perimeter.]]></description>
            <link>https://sph.sh/en/posts/serverless-internal-service-layer-105-multi-account/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-internal-service-layer-105-multi-account/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[vpc]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Parts 101 through 104 chose this layer, settled its protocols, built it, and secured it, all inside a single AWS account. Multi-team estates do not stay there. The team boundary tends to land on an account boundary, and that one move rewrites the authorization rules underneath the layer. Same-account, the API's resource policy and the caller's identity policy are an OR: either one can allow the call. Cross-account they become an AND: both must explicitly allow it, and silence denies. Everything else in this part follows from that flip, including the two network topologies you can choose between, the policies that carry the data perimeter, and the one guardrail you cannot write at the organization level. Every rule below is pinned to AWS documentation.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How the Layer Fails: Throttles, Retries, and Loops]]></title>
            <description><![CDATA[API Gateway shares one throttle bucket with your front door, never retries a Lambda integration, and cannot see loops through itself. What you have to rebuild.]]></description>
            <link>https://sph.sh/en/posts/serverless-internal-service-layer-106-failure-modes/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-internal-service-layer-106-failure-modes/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[vpc]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The layer is built, its identity is settled both ways, and it spans accounts. Now the honest part: how it fails. Putting a gateway between every internal call does more than add a per-call price. It changes what failure looks like, and it quietly removes two things the direct invoke gave you for nothing: Lambda's recursive-loop circuit breaker, and any retry at all. The layer from part 103 is still worth building. The point of this part is that you now own what it took away, and the missing pieces are not the ones most write-ups warn about.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Observability, and Where Service Meshes Are Heading]]></title>
            <description><![CDATA[The access log already names the caller, route, and latency. Per-route metrics are not free, the user token needs masking, and AWS is switching off its own mesh.]]></description>
            <link>https://sph.sh/en/posts/serverless-internal-service-layer-107-observability/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-internal-service-layer-107-observability/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[vpc]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A service mesh sells three things: identity, resilience, and observability. This series composed the first two out of primitives that were already in the account: identity in part 104, resilience in part 106. Observability is the third, and it is the one people assume arrives for free. Half of it does. Without a line of application code, every call writes one JSON line naming the calling service, the route, the status, and the latency. No sidecar anywhere.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[CDK TypeScript Lambda: Choosing a Bundler and cdk synth Runner]]></title>
            <description><![CDATA[A measured benchmark of 9 bundlers and 3 cdk synth runners for CDK TypeScript Lambdas, with a per-layer default and the rule that picks each one.]]></description>
            <link>https://sph.sh/en/posts/cdk-lambda-bundler-runner-benchmark/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/cdk-lambda-bundler-runner-benchmark/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[esbuild]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[build-tools]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A CDK TypeScript Lambda project looks like one tooling decision and is really three: the runtime that runs your handler, the bundler that packs it, and the runner that executes cdk synth. These layers are loosely coupled, so each has a different winner. For a greenfield project the defaults are stock nodejs22.x, esbuild through NodejsFunction, and tsx for synth; the rule behind all three is that the closer a layer sits to production runtime, the more boring its tool should be. Nine bundlers and three synth runners, measured on eight realistic handlers, justify each default and mark where you override it. Numbers come from one Linux arm64 sandbox (Node v22.22.3, aws-cdk-lib 2.261.0, medians of two or three runs), so the ratios travel while the absolute milliseconds do not.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Nub vs Vite+: Two Rust Toolchains for Opposite Ends of the JS Stack]]></title>
            <description><![CDATA[Nub and Vite+ are both 2026 oxc-powered Rust toolchains that look like rivals but are not. A clear rule for which binary belongs in which repo.]]></description>
            <link>https://sph.sh/en/posts/nub-vs-vite-plus-javascript-toolchain/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/nub-vs-vite-plus-javascript-toolchain/</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[vite]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[build-tools]]></category>
            <category><![CDATA[oxc]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[package-manager]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The JavaScript toolchain is still a stack of separate parts: a package manager, a runtime, a TypeScript runner, a bundler, a test runner, a linter, and a formatter, each with its own config file and its own version drift. Two 2026 projects promise to collapse that pile into one fast Rust binary: Nub and Vite+. Both are oxc-powered, single-binary, and shipped weeks apart, so engineers keep filing them as rivals. They are not. Vite+ owns the browser-facing application pipeline; Nub owns the stock-Node process. The rule is that simple: if your build output loads in a browser, standardize on Vite+; if it runs as a Node process, reach for Nub. All the numbers below come from vendor micro-benchmarks on specific scenarios.</p>
<p><strong>What Vite+ owns</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Should Stateful Resources Live in a Separate CDK Stack?]]></title>
            <description><![CDATA[A lifecycle test for CDK stack layout: give a resource its own long-lived stack when it outlives any single deployer, then reach it by a well-known name.]]></description>
            <link>https://sph.sh/en/posts/cdk-stateful-stack-named-contracts/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/cdk-stateful-stack-named-contracts/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[cloudformation]]></category>
            <category><![CDATA[infrastructure-as-code]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 03 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A CDK app answers one layout question over and over: does this resource share a stack with the Lambda functions that use it, or get a stack of its own? The usual advice, "keep the database separate," reaches the right answer through the wrong test. State is not the criterion. A shared EventBridge bus holds no data, yet deleting it breaks every domain at once, so asking "does it hold state" misclassifies it. The criterion that predicts placement is lifecycle: a resource earns its own long-lived stack when its lifecycle outlives any single deployer, and other stacks then reach it by a well-known name. That name is a contract, and the contract sits on a coupling spectrum you can tune.</p>
<p><strong>The lifecycle test</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Managing IAM Policies and Roles at Scale Without Hitting AWS Limits]]></title>
            <description><![CDATA[The exact IAM size, attach, and quota limits you will hit at scale, and the scoped-policy, permission-boundary, and SCP structure that keeps you far from every one.]]></description>
            <link>https://sph.sh/en/posts/iam-policies-roles-at-scale-aws-limits/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/iam-policies-roles-at-scale-aws-limits/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[iam]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[least-privilege]]></category>
            <category><![CDATA[permission-boundaries]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 01 Jul 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>IAM has hard, quiet ceilings. A managed policy can hold only so many characters. A role can carry only so many attached policies. Inline policies share a per-entity size budget that fills up without warning. Accounts have a default role quota. Teams that lean on one shared mega-role and paste permissions through the console drift toward these ceilings, then get blocked mid-deployment with a cryptic error. The way out is structural: least privilege as the default, one policy per responsibility, scoped roles instead of a shared mega-role, and permission boundaries plus SCPs as guardrail layers.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Backstage Build vs Buy: The Real Cost of Running Your Own IDP]]></title>
            <description><![CDATA[Backstage looks like a quick install, but the recurring cost is a standing platform team. A leader's guide to deciding DIY Backstage versus a hosted IDP.]]></description>
            <link>https://sph.sh/en/posts/backstage-build-vs-buy/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/backstage-build-vs-buy/</guid>
            <category><![CDATA[backstage]]></category>
            <category><![CDATA[platform-engineering]]></category>
            <category><![CDATA[developer-experience]]></category>
            <category><![CDATA[internal-developer-platform]]></category>
            <category><![CDATA[golden-paths]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Service sprawl is the problem leaders reach for Backstage to solve: dozens or hundreds of services, and nobody can say what runs where or who owns it. The trap is that the tool demos as a quick install, so the budget gets written against the install rather than the thing that keeps it alive. The honest framing is this: Backstage is a framework you staff, not a product you buy, and the real recurring line item is a standing platform team. The decision that follows is whether to run Backstage yourself or adopt a hosted internal developer platform; without a funded platform team already in place, hosted is the default.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Bruno API Collections in Git: A Workflow Shift, Not Free Postman]]></title>
            <description><![CDATA[Committing Bruno .bru files to the repo keeps the API contract in the same PR and history as the code. The only real tax is a deliberate secrets boundary.]]></description>
            <link>https://sph.sh/en/posts/bruno-api-collections-in-git/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/bruno-api-collections-in-git/</guid>
            <category><![CDATA[bruno]]></category>
            <category><![CDATA[api-testing]]></category>
            <category><![CDATA[git]]></category>
            <category><![CDATA[developer-experience]]></category>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[postman]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 29 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Kinesis Explained: Data Streams vs Firehose vs Managed Flink]]></title>
            <description><![CDATA[Kinesis is four AWS services under one name. A guide to the four, the Data Streams shard engine underneath, its cost shape, and when to pick something else.]]></description>
            <link>https://sph.sh/en/posts/aws-kinesis-family-deep-dive/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-kinesis-family-deep-dive/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[kinesis]]></category>
            <category><![CDATA[data-streams]]></category>
            <category><![CDATA[streaming]]></category>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[serverless]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 28 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>"Kinesis" is one of the most overloaded names in AWS. The name covers four separate services that solve different problems, so a team that picks "Kinesis" from a console dropdown often discovers later that it cannot do what they assumed. When an engineer says "we'll put it on Kinesis," they almost always mean <strong>Data Streams</strong>: the raw, shard-based, ordered, replayable log. The other three are a delivery pipe, a stream-processing engine, and a video service. What follows disambiguates the four, then goes deep on the Data Streams mechanics, cost shape, and decision boundaries you need before you commit.</p>
<p><strong>The four services, disambiguated</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Blameless Postmortems: A Model and a Copy-Paste Template]]></title>
            <description><![CDATA[A blameless postmortem model that fixes the system instead of finding a culprit, with a copy-paste template and where individual accountability still applies.]]></description>
            <link>https://sph.sh/en/posts/blameless-culture-postmortem-thinking/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/blameless-culture-postmortem-thinking/</guid>
            <category><![CDATA[engineering-culture]]></category>
            <category><![CDATA[incident-response]]></category>
            <category><![CDATA[psychological-safety]]></category>
            <category><![CDATA[reliability]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The same outage keeps coming back because your incident review found a culprit instead of a cause. That is the failure mode to fix. An organization does not get safer by naming the person who typed the wrong command. It gets safer when the same wrong command can no longer take the system down. "Who did it" feels like an answer, so it closes the review. "What allowed it" stays open and forces a system change. The model that follows targets controls instead of people, with a copy-paste template and a clear line for where individual accountability still applies.</p>
<p>The operating model holds up across teams, but "blameless" is a culture change. The metrics that prove it landed take a few quarters to move.</p>
<p><strong>Two Question Sets, Two Outcomes</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Engineering Team Documentation: Working Agreements, Definition of Done, and On-Call Docs]]></title>
            <description><![CDATA[The team documents a mature engineering team owns: onboarding, working agreements, Definition of Done, on-call, knowledge transfer, and what makes each one good.]]></description>
            <link>https://sph.sh/en/posts/team-documentation-ways-of-working/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/team-documentation-ways-of-working/</guid>
            <category><![CDATA[engineering-culture]]></category>
            <category><![CDATA[onboarding]]></category>
            <category><![CDATA[documentation]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[knowledge-sharing]]></category>
            <category><![CDATA[team-topologies]]></category>
            <category><![CDATA[leadership]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A team's ways of working usually live in people's heads. The result is predictable: every new joiner gets a different, lossy version of "how we do things," on-call is stressful because the expectations were never written, and a key person leaving takes months of context with them. The fix is not "write more docs"; it is to treat ways-of-working as a small, named set of living documents, each with a single owner, stored next to the work, with onboarding delivered as a tracked, checklisted program rather than a buddy ritual. What follows is a taxonomy of the specific documents a healthy team owns, with a stance on what makes each one good versus its common failure mode.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Triggering AppSync Subscriptions From Outside AppSync]]></title>
            <description><![CDATA[AppSync subscriptions fire only on mutations. This explores bridging downstream BFF events into a NONE-data-source mutation with EventBridge and CDK.]]></description>
            <link>https://sph.sh/en/posts/appsync-graphql-bff-subscriptions/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/appsync-graphql-bff-subscriptions/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[appsync]]></category>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[eventbridge]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[real-time]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 19 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AWS AppSync gives you managed WebSocket subscriptions, but they fire only as a response to an AppSync GraphQL mutation. In a multi-service Backend-for-Frontend (BFF), the truth lives downstream in the order, inventory, and payments services, and those state changes never pass through an AppSync mutation. So "managing subscriptions" in a BFF is really one problem: bridging downstream events into an internal mutation backed by a NONE data source, then letting AppSync fan the payload out. The wiring below is an exploration in AWS CDK; none of it has been deployed or measured yet.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GitHub Actions Triggers: push vs pull_request vs Merge Queue vs Release]]></title>
            <description><![CDATA[Each git event deserves a different GitHub Actions job: what to run on push, pull_request, the merge queue, and tag/release, and why routing protects lead time.]]></description>
            <link>https://sph.sh/en/posts/ci-steps-by-trigger/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/ci-steps-by-trigger/</guid>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[continuous-delivery]]></category>
            <category><![CDATA[dora-metrics]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 19 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Push a feature commit, open a pull request, or cut a release, and many pipelines respond the same way: the full test matrix, the integration suite, and the deploy logic all fire. That uniformity is the problem. Each git event arrives at a different point in the lifecycle and carries a different security context, so a check that belongs at one trigger is either wasteful or dangerous at another. Treat the on: block as a routing table instead: match the check to the trigger so PR feedback stays fast, main stays deployable, and releases stay reproducible.</p>
<p>The through-line is the same one in Compressing Time to Production: protecting lead time. Running everything on every push does not make delivery safer; it slows feedback and leaks privilege into code you have not reviewed yet.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Server-Driven UI for Native Mobile Apps]]></title>
            <description><![CDATA[Server-Driven UI is the mobile analog of server-side composition. The hard part is not JSON rendering but a versioned component contract that survives old app versions.]]></description>
            <link>https://sph.sh/en/posts/server-driven-ui-mobile/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/server-driven-ui-mobile/</guid>
            <category><![CDATA[server-driven-ui]]></category>
            <category><![CDATA[sdui]]></category>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 19 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>You ship a native app, then need to change a merchandising screen, a promo banner, or a checkout step every week. Each change waits on app-store review and on users updating, and the app versions already installed stay in users' pockets for months, so you cannot roll the client back the way you redeploy a server. Server-Driven UI (SDUI) answers this by shipping a UI <em>description</em> instead of new native code: the recommendation here is to treat it as a scalpel for content-shaped surfaces and design the contract so old clients degrade gracefully.</p>
<p><strong>The Web Analog and Mobile's Extra Constraint</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Reduce Lead Time from Commit to Production]]></title>
            <description><![CDATA[How high-performing teams shrink the lead time from code-complete to live in production, without trading away security or code quality. A guide for tech leads.]]></description>
            <link>https://sph.sh/en/posts/compressing-time-to-production/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/compressing-time-to-production/</guid>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[dora-metrics]]></category>
            <category><![CDATA[feature-flags]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[trunk-based-development]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In many organizations a feature can be code-complete for days before it is live, stuck behind a review queue, a release train, or a deploy slot it missed. The metric that captures this is lead time for changes: the span from a change being committed to running in production, and most of that clock is process, not programming. The most useful move a tech lead can make is to treat lead time as the primary delivery metric and attack its biggest wait-state structurally. The order is fixed: shrink batch size, decouple deploy from release, then turn the security gate and the cross-team gate into self-service guardrails.</p>
<p><strong>The Lead-Time Clock</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Shift-Left Security: Removing the Review-Queue Bottleneck]]></title>
            <description><![CDATA[How high-performing teams keep security review from becoming a lead-time bottleneck: shift-left automation, risk-based gates, a paved road, dependency cadence.]]></description>
            <link>https://sph.sh/en/posts/security-without-the-review-queue/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/security-without-the-review-queue/</guid>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[dependency-management]]></category>
            <category><![CDATA[dora-metrics]]></category>
            <category><![CDATA[software-architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In many organizations a finished change waits days in a review queue before it can ship. The queue exists because security review is treated as a uniform manual gate: every change, regardless of risk, lines up behind a human before it reaches production. The fix is to stop treating review as a per-change gate: convert it into automated, risk-based guardrails so the common case never waits on a person. The queue then disappears without weakening security, and the gate stays only where the risk earns it.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mobile API Versioning with a BFF: Shipping When You Can't Roll Back the Client]]></title>
            <description><![CDATA[A mobile binary can't be rolled back and old versions linger, so safety and speed move server-side: a BFF, consumer-driven contracts, and backward-compatible versioning.]]></description>
            <link>https://sph.sh/en/posts/the-client-you-cannot-roll-back/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/the-client-you-cannot-roll-back/</guid>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[bff]]></category>
            <category><![CDATA[api-versioning]]></category>
            <category><![CDATA[contract-testing]]></category>
            <category><![CDATA[software-architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>On the server, a bad deploy is reversible in seconds; on mobile, a shipped binary is permanent, app-store review sits outside your control, and old client versions stay in use for months. That asymmetry is the design constraint that should drive your release strategy: the client is the one part of the system you cannot roll back, so both safety and speed have to move to the side you can change. The levers that follow from that constraint are a backend-for-frontend seam, consumer-driven contracts for independent deployability, backward-compatible API evolution, and a clear line for where server-driven UI earns its keep.</p>
<p><strong>The Asymmetric Clock</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lambda Durable Functions vs Step Functions: When the Orchestrator Belongs in Your Code]]></title>
            <description><![CDATA[Lambda durable functions put the orchestrator in your TypeScript. Reach for them when you own the whole workflow; keep Step Functions for cross-team state machines.]]></description>
            <link>https://sph.sh/en/posts/lambda-durable-functions-vs-step-functions/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/lambda-durable-functions-vs-step-functions/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[step-functions]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[saga-pattern]]></category>
            <category><![CDATA[durable-functions]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Problem</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Golden Paths for a Frontend Platform: Scaffolding, Shared Packages, and a Task CLI]]></title>
            <description><![CDATA[How a frontend platform team makes the right way the easy way: golden-path scaffolding, versioned shared packages, and a task CLI that removes per-team drift.]]></description>
            <link>https://sph.sh/en/posts/paved-road-frontend-platform/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/paved-road-frontend-platform/</guid>
            <category><![CDATA[platform-engineering]]></category>
            <category><![CDATA[developer-experience]]></category>
            <category><![CDATA[monorepo]]></category>
            <category><![CDATA[design-system]]></category>
            <category><![CDATA[golden-paths]]></category>
            <category><![CDATA[frontend-architecture]]></category>
            <category><![CDATA[internal-developer-platform]]></category>
            <category><![CDATA[storybook]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>When an organization runs many small frontend apps, each team starts from a different scaffold and the build config, lint rules, and deploy shape drift apart within a few sprints. The drift is not laziness: there is no single place where the standard way to start an app is also the fastest way to start one, so every team rebuilds the basics and reimplements observability, i18n, and deployment slightly differently. The fix is a thin paved road built from three primitives (a golden-path skeleton, versioned shared packages, and a task CLI) where the only metric that proves it works is voluntary adoption, not a coverage mandate.</p>
<p><strong>Why Per-Team Drift Accumulates</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hexagonal Architecture for a Presentation Service vs BFF]]></title>
            <description><![CDATA[A presentation service is the thin backend behind a UI fragment. Ports-and-adapters keeps it from rotting into glue code by pointing dependencies inward.]]></description>
            <link>https://sph.sh/en/posts/hexagonal-presentation-service/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/hexagonal-presentation-service/</guid>
            <category><![CDATA[hexagonal-architecture]]></category>
            <category><![CDATA[ports-and-adapters]]></category>
            <category><![CDATA[clean-architecture]]></category>
            <category><![CDATA[node]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[bff]]></category>
            <category><![CDATA[domain-driven-design]]></category>
            <category><![CDATA[observability]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 12 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A presentation service is the thin backend that sits behind a single UI fragment: it formats money and dates, resolves copy and i18n, shapes session state, and proxies one downstream domain service on the UI's behalf. Teams treat it as a dumping ground for glue code, and that glue rots into a mud-ball. Money formatting gets tangled into HTTP handlers, and the downstream client's quirks leak into business rules. The discipline that keeps this service maintainable is ports-and-adapters (hexagonal architecture), which reduces to one rule: force the domain core to depend on nothing concrete.</p>
<p><strong>The Glue-Code Mud-Ball</strong></p>
<p>The failure mode is predictable. A service that starts as a "thin proxy" quietly accretes responsibilities until no boundary survives:</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Server-Side Micro-Frontend Composition: One Page, Many Teams]]></title>
            <description><![CDATA[Server-side micro-frontend composition lets independent teams own fragments of one page. The hard part is the ownership boundary and the versioned consistency contract.]]></description>
            <link>https://sph.sh/en/posts/server-side-micro-frontend-composition/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/server-side-micro-frontend-composition/</guid>
            <category><![CDATA[micro-frontends]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[ssr]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 09 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A single high-traffic page, such as a checkout or storefront, often needs contributions from several teams who must ship independently. A frontend monolith forces them onto one release train, so one team's slow build or broken test blocks every other team's deploy. The mechanism that assembles the page is the easy part; the genuinely hard problem is the ownership boundary plus a versioned consistency contract that keeps many teams visually and behaviourally aligned without a coordinated release. For a multi-team, SEO-sensitive page where first paint matters, the default is server-side composition; client-side composition and iframes win narrower cases.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Code Architecture by Init Amortization: Lean on Lambda, Heavy Only When Earned]]></title>
            <description><![CDATA[Match architecture weight to each runtime's init-amortization: lean handlers on single-purpose Lambda, more on a Lambdalith, full OOP/DI only on long-lived runtimes.]]></description>
            <link>https://sph.sh/en/posts/code-architecture-init-amortization/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/code-architecture-init-amortization/</guid>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[functional-programming]]></category>
            <category><![CDATA[dependency-injection]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Problem</strong></p>
<p>Part 1 answered the topology half of the Lambda question: single responsibility is a code principle, and how many functions to deploy is a separate topology decision. The other half is how much code architecture belongs inside a given runtime: how much OOP, dependency injection, and framework weight. The answer mirrors Part 1's "earn the exception" shape: default to lean functional handlers, then earn heavier structure as each runtime's init-amortization permits. The variable that decides it is how many invocations one initialization serves.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Effect on AWS Lambda: Building a URL Shortener with SST and DynamoDB]]></title>
            <description><![CDATA[A small, complete URL shortener on AWS Lambda and DynamoDB with Effect and SST v4, showing schema-at-the-boundary, layers, and tagged-error mapping.]]></description>
            <link>https://sph.sh/en/posts/effect-lambda-link-shortener/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/effect-lambda-link-shortener/</guid>
            <category><![CDATA[effect]]></category>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[sst]]></category>
            <category><![CDATA[dynamodb]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A typical TypeScript Lambda handler accretes cross-cutting work inline: JSON.parse(event.body), ad-hoc if (!body.url) checks, URL validation in a try/catch, then business logic wrapped in more try/catch blocks where each catch re-derives an HTTP status and re-serializes an error body. The non-obvious cost is not the boilerplate; it is that statuses and error shapes get chosen at the call site, so they drift. Worse, the only way to test the logic is to either hit real AWS or hand-mock the SDK client. Effect collapses that pile into a thin adapter over a typed program. One buildable artifact, a URL shortener on Lambda and DynamoDB wired with SST v4, shows exactly what each Effect primitive buys you.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Lambda: Single-Purpose Functions vs Lambdalith]]></title>
            <description><![CDATA[How to slice AWS Lambda functions: default to single-purpose, treat the single-domain Lambdalith as an earned exception, and the platform forces that decide it.]]></description>
            <link>https://sph.sh/en/posts/lambda-function-granularity-responsibility-scope-domain/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/lambda-function-granularity-responsibility-scope-domain/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[cold-start]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Problem</strong></p>
<p>When teams build an HTTP API on AWS Lambda, an early decision quietly sets the shape of everything that follows: how many functions to deploy. Tutorials map one route to one function, so teams copy that and reach 60 functions per service, each with its own IAM role, bundling config, and a slice of a sprawling template. The consolidation backlash answers with a "Lambdalith": one function per service running an in-process router. My position is the opposite of that backlash. Default to single-purpose functions (one per route, one per event), and treat the single-domain Lambdalith as a consolidation you earn, not a starting point. The spectrum comes first, then the platform forces (cold start, concurrency, IAM, observability) that decide where on it you land...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Scrum vs Kanban vs Scrumban for AI-Assisted Teams]]></title>
            <description><![CDATA[AI absorbs more of the implementation, so the framework name matters less than four feedback loops. A decision lens for tuning flow, cadence, WIP, and review.]]></description>
            <link>https://sph.sh/en/posts/scrum-kanban-scrumban-ai-driven-teams/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/scrum-kanban-scrumban-ai-driven-teams/</guid>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[agile]]></category>
            <category><![CDATA[scrum]]></category>
            <category><![CDATA[kanban]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[engineering-culture]]></category>
            <category><![CDATA[dora]]></category>
            <category><![CDATA[developer-productivity]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AI assistants now write a growing share of the code. Engineering leaders still burn weeks arguing whether to adopt Scrum, Kanban, or some other agile framework, then copy another company's ceremonies and wonder why nothing flows faster. The trap is treating the framework name as the decision; the name is a wrapper around four feedback loops, and those loops are what actually move work. My stance is that an AI-assisted team almost never needs a branded methodology: it needs to tune how work flows, how often you re-steer, how much work runs in parallel, and how output gets reviewed. That decision lens outlives the next framework fad and accounts for AI moving the bottleneck off typing code.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Zod Branded Types for PII Protection: Compile-Time Log Safety]]></title>
            <description><![CDATA[Bake a single PII branded type into your observability API signatures so TypeScript rejects sensitive fields at the call site, before any runtime redactor sees them.]]></description>
            <link>https://sph.sh/en/posts/zod-brands-prevent-pii-logging/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/zod-brands-prevent-pii-logging/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[zod]]></category>
            <category><![CDATA[logging]]></category>
            <category><![CDATA[pino]]></category>
            <category><![CDATA[sentry]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[pii]]></category>
            <category><![CDATA[gdpr]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 28 May 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Structured logs leak PII through three near-identical call sites: logger.info({ user }), metrics.increment("login", { email }), and tracer.startActiveSpan("auth", { attributes: { ip } }). Most teams add Pino redact.paths or a cloud-side scrubber to catch these. Those defenses are last-line and list-based, and they drift silently as schemas evolve. Effect-TS handles the same problem at runtime, with Redacted<A> wired into its Logger. Pino covers a slice of it at compile time with LogFnFields, banning specific keys via never. Rust's secrecy crate takes a third route: the wrapper never implements Display.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Staff Engineer Solver Archetype: An Operating Model]]></title>
            <description><![CDATA[A handbook for the informal fast track: recognise the Solver role, codify its operating model before the role calcifies, and time it against the title-and-pay talk.]]></description>
            <link>https://sph.sh/en/posts/solver-track-staff-engineer-operating-model/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/solver-track-staff-engineer-operating-model/</guid>
            <category><![CDATA[staff-engineer]]></category>
            <category><![CDATA[engineering-management]]></category>
            <category><![CDATA[organizational-design]]></category>
            <category><![CDATA[compliance]]></category>
            <category><![CDATA[career]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>The informal fast track most engineering organisations end up with</strong></p>
<p>In most engineering organisations past a certain size, a second track quietly forms next to the standard SDLC: work that is too strategic to wait, too uncertain to schedule, too narrow to staff a full team for. The pattern repeats independently of industry: executive sponsorship, one or two senior engineers, a runway measured in months, and a small set of recognisable outcomes. Most of these tracks fail not because of execution but because nobody wrote the operating model before the first project entered the lane.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Aurora Serverless v2 Works: Architecture Deep Dive]]></title>
            <description><![CDATA[What Aurora Serverless v2 is under the hood: the shared storage layer, ACU-driven compute, the Caspian substrate, scale-to-zero, and mixed-mode clusters.]]></description>
            <link>https://sph.sh/en/posts/aurora-serverless-v2-architecture/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aurora-serverless-v2-architecture/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aurora]]></category>
            <category><![CDATA[aurora-serverless-v2]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[mysql]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Aurora Serverless v2 is often introduced as if it were a separate database product, distinct from provisioned Aurora. That framing leads engineers to wrong assumptions: that read replicas autoscale by count, that scaling events drop connections, that every scale-up warms a cold cache. None of those hold, though a fourth is partially true since November 2024: scale-to-zero does imply a cold-start path. Underneath, Serverless v2 is a scaling daemon and a billing wrapper over the same Aurora compute and storage that provisioned clusters run on.</p>
<p><strong>A note on the name</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Phronesis and AI Coding Agents: The Skill the Model Cannot Give You]]></title>
            <description><![CDATA[Agents made code-writing essentially free, but judgment about when and how much to use them is still entirely yours. An Aristotelian frame to separate the two skills.]]></description>
            <link>https://sph.sh/en/posts/phronesis-and-ai-coding-agents/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/phronesis-and-ai-coding-agents/</guid>
            <category><![CDATA[ai-tools]]></category>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[ai-agents]]></category>
            <category><![CDATA[agentic-ai]]></category>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AI coding agents have collapsed the cost of writing code to roughly zero. The cost of knowing when and how much code to write has not moved. Most of the current "AI productivity" debate is people talking past each other because they are mixing those two skills together. One frame, borrowed from Aristotle, separates them cleanly; read Mario Zechner, Addy Osmani, Kent Beck, Simon Willison, METR, and Steve Yegge through it and the apparent contradictions dissolve into a single disagreement. On that disagreement, Zechner is closer to right than Yegge is, but Yegge's foil sharpens the point rather than blunting it.</p>
<p><strong>The Two Skills the Debate Keeps Confusing</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GitHub Environments: Manual Approval Gates for Deployments]]></title>
            <description><![CDATA[Production deploys need a real approval gate: use GitHub Environments with native protection rules and scoped secrets, not workflow if: hacks or marketplace actions.]]></description>
            <link>https://sph.sh/en/posts/github-environments-approval-gates/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/github-environments-approval-gates/</guid>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[deployment]]></category>
            <category><![CDATA[security]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 19 May 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Production deploys need a human in the loop, and most teams reach for the wrong primitive. The common shapes are a workflow if: condition that checks a label, a marketplace manual-approval action that blocks on issues: write, or a workflow_dispatch button that any contributor can fire. Build the artifact once from main, deploy it to staging automatically, and promote the same artifact to production only after a PM or QA approves at a GitHub Environment gate. Same artifact, two environments, one approval. That gate covers the deploy credential and the human action at once, and it leaves an audit row tied to the run.</p>
<p><strong>The Environment object</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build a RAG Agent with AWS Bedrock and CDK]]></title>
            <description><![CDATA[Building a RAG agent on AWS Bedrock + Knowledge Bases + OpenSearch Serverless with CDK in TypeScript: architecture, IAM wiring, automated ingestion, and the chat UI.]]></description>
            <link>https://sph.sh/en/posts/aws-bedrock-rag-cdk-port/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-bedrock-rag-cdk-port/</guid>
            <category><![CDATA[aws-bedrock]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[rag]]></category>
            <category><![CDATA[opensearch-serverless]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[iam]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AWS markets Bedrock Agent plus Knowledge Bases plus OpenSearch Serverless as the managed-RAG path: ship retrieval-grounded chat without owning the retrieval code. I ported the DigitalOcean rag-assistant blueprint shape onto AWS-native services with CDK in TypeScript, then deployed it end-to-end and watched the first run fail. The AWS Bedrock managed-RAG stack works with CDK, but two doc-gap traps will fail your first deploy: a cross-region inference-profile IAM hole, and the absence of an automatic ingestion job.</p>
<p><strong>Architecture mapping</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Amazon Bedrock Knowledge Bases Explained: Data Sources and Vector Stores]]></title>
            <description><![CDATA[What a Bedrock Knowledge Base really is, which data sources and vector stores are first-class, and why the console default rarely fits a small corpus.]]></description>
            <link>https://sph.sh/en/posts/bedrock-knowledge-bases-anatomy/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/bedrock-knowledge-bases-anatomy/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-bedrock]]></category>
            <category><![CDATA[rag]]></category>
            <category><![CDATA[vector-databases]]></category>
            <category><![CDATA[opensearch]]></category>
            <category><![CDATA[knowledge-base]]></category>
            <category><![CDATA[confluence]]></category>
            <category><![CDATA[embeddings]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>The default behind the console wizard</strong></p>
<p>Amazon Bedrock Knowledge Bases packages a connector, a chunker, an embedding model, a vector store, and a retrieval API into one managed surface. The Confluence connector is a vector-store decision in disguise. AWS documents that Confluence Cloud is "currently" only available with the OpenSearch Serverless vector store, and the same lock-in applies to SharePoint and Salesforce. That single constraint splits the architecture into two paths, and choosing before you wire the connector saves a rewrite.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[HMAC: What It Is, How It Works, and When It Is the Wrong Tool]]></title>
            <description><![CDATA[HMAC-SHA-256 for webhooks, signed URLs, and internal auth, with runnable code in three languages and the boundary where digital signatures take over.]]></description>
            <link>https://sph.sh/en/posts/hmac-explainer-when-to-use/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/hmac-explainer-when-to-use/</guid>
            <category><![CDATA[security]]></category>
            <category><![CDATA[cryptography]]></category>
            <category><![CDATA[hmac]]></category>
            <category><![CDATA[webhooks]]></category>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Webhook receivers, signed download URLs, and internal service calls all share one question: did this request actually come from the party we share a secret with, and did it arrive intact? A constant-time string compare, raw-body hashing, and clock-skew handling have to be right at the same time, or the protection is decorative. For symmetric-trust integrity where both sides can hold the same secret, HMAC-SHA-256 with the standard library's constant-time compare is the right default.</p>
<p><strong>HMAC defined</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kafka vs SNS/SQS/EventBridge: When to Migrate and How]]></title>
            <description><![CDATA[Named signals that justify a Kafka migration from a managed event bus, and a four-phase outbox-anchored playbook to move without rip-and-replace.]]></description>
            <link>https://sph.sh/en/posts/kafka-or-event-bus-migration-signals/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/kafka-or-event-bus-migration-signals/</guid>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[sns]]></category>
            <category><![CDATA[sqs]]></category>
            <category><![CDATA[eventbridge]]></category>
            <category><![CDATA[migration]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Teams running on SNS+SQS, EventBridge, Pub/Sub, or Service Bus eventually hit a wall and ask whether they should move to Kafka. The hard part is that "it depends" answers never enumerate which conditions actually matter, so the decision drifts toward whichever vendor doc was read last. Kafka solves durability, replay, ordering, and sustained throughput. Stay on the managed event bus until two or more concrete signals fire, then migrate incrementally with a transactional outbox rather than a rip-and-replace.</p>
<p>Seven signals justify the move, five counter-signals argue against it, and the migration playbook below moves one event type at a time so rollback stays possible at every step.</p>
<p><strong>Signals That Push You to Kafka</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deploying AWS Bedrock AgentCore with CDK: a quickstart]]></title>
            <description><![CDATA[A CDK guide for deploying a minimal Strands agent on AgentCore Runtime: parameterized stack, arm64 build, deploy and invoke, with IAM and Marketplace prerequisites.]]></description>
            <link>https://sph.sh/en/posts/agentcore-cdk-quickstart/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/agentcore-cdk-quickstart/</guid>
            <category><![CDATA[aws-bedrock]]></category>
            <category><![CDATA[ai-agents]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[infrastructure-as-code]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 05 May 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>The setup</strong></p>
<p>A companion post walks through what AgentCore Runtime is at the architecture level: container, identity, memory, gateway. The deployment question starts where that ends: AgentCore is now reachable from CDK through @aws-cdk/aws-bedrock-agentcore-alpha, so what does a working trial look like end-to-end? Short answer: take the alpha L2 and pin its version. The stack code stays short because the construct carries most of the weight, but a few IAM and Marketplace prerequisites need to land before the first invoke returns.</p>
<p><strong>What we are building</strong></p>
<p>A single CDK stack that deploys a minimal Strands agent on AgentCore Runtime in eu-central-1, plus the build script and the boto3 invoke helper. The full layout:</p>
<p>[code block]</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Set Up Claude as a PR Reviewer with the Official GitHub Action]]></title>
            <description><![CDATA[A hardened, paste-ready setup for adding Anthropic's claude-code-action to a GitHub repo, with the security and cost knobs spelled out for production use.]]></description>
            <link>https://sph.sh/en/posts/claude-code-action-pr-reviewer-setup/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/claude-code-action-pr-reviewer-setup/</guid>
            <category><![CDATA[claude]]></category>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[anthropic]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Adding AI-assisted code review to a GitHub repository starts with one choice: an ad-hoc LLM integration, or a maintained action. The maintained path already handles access control and prompt-injection sanitization; the ad-hoc one leaves both to you. anthropics/claude-code-action v1 is the default worth starting from, configured with a pull_request trigger, a read-only tool allowlist, a pinned model, and a merge gate that stays human.</p>
<p>The workflow file itself is short. The judgment sits in what surrounds it: which trigger fires it, which permissions it gets, which tools the model may call, and what a large PR costs per review.</p>
<p><strong>The recommended setup</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Lambda Cold Start Optimization in TypeScript: 5 Anti-Patterns]]></title>
            <description><![CDATA[DI containers, monolithic SDKs, god-handlers, top-level secret fetches, and heavy ORMs: what they cost on cold start, and the functional shape that replaces them.]]></description>
            <link>https://sph.sh/en/posts/aws-lambda-typescript-anti-patterns/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-lambda-typescript-anti-patterns/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[cold-start]]></category>
            <category><![CDATA[bundle-size]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Teams move from NestJS, Spring, or .NET monoliths to AWS Lambda and bring patterns that work in long-running services. Bundles bloat, cold starts grow, and the platform's economics fight back. Lambda is a function, not a microservice; monolith OO/DI patterns inflate bundles and tank cold starts.</p>
<p>Five habits cause most of the damage: DI containers, non-modular AWS SDK imports, god-handler service classes, synchronous secret fetches at module top, and heavy ORMs.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deploying a WASM Image-Resize Module to Cloudflare Workers]]></title>
            <description><![CDATA[An exploration of whether a Rust + WASM image-resize handler fits inside Cloudflare Workers' binary-size, memory, and CPU ceilings before the POC runs.]]></description>
            <link>https://sph.sh/en/posts/cloudflare-workers-wasm-image-resize/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/cloudflare-workers-wasm-image-resize/</guid>
            <category><![CDATA[webassembly]]></category>
            <category><![CDATA[wasm]]></category>
            <category><![CDATA[cloudflare-workers]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[edge-computing]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A POST /resize?w=800&q=85 handler that takes a JPEG in the body and returns a resized JPEG is the simplest non-toy test of Cloudflare Workers + WASM as an image pipeline host. The design pushes three edge-platform limits at once: compiled binary size, per-isolate memory, and per-request CPU time. Read in order, the published limits already settle most of the question. The Free tier is out on CPU time alone, and on the Paid tier the design fits for one image per request, inside a bundle budget that leaves little room for a second codec.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[WebAssembly Use Cases: Browser, WASI Server, and Edge Compute]]></title>
            <description><![CDATA[A stack-agnostic map of WebAssembly's three bets (browser performance, server-side WASI, edge compute) so you can tell which one a Wasm talk is about.]]></description>
            <link>https://sph.sh/en/posts/webassembly-101/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/webassembly-101/</guid>
            <category><![CDATA[webassembly]]></category>
            <category><![CDATA[wasm]]></category>
            <category><![CDATA[wasi]]></category>
            <category><![CDATA[edge-computing]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>WebAssembly conversations keep talking past each other because a single word covers three very different architectural bets. A frontend engineer hears "makes the web fast," a backend engineer hears "replaces containers," a platform engineer hears "powers the edge," and all three are partially right. Match your problem to one of the three before evaluating a single runtime; if none of them describes your architecture, the honest answer is to skip Wasm for now.</p>
<p><strong>WASM in 30 Seconds</strong></p>
<p><strong>One bytecode, three separate bets: performance in the browser, a WASI runtime on the server, compute at the edge. Each bet answers a different question.</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[EventBridge Cross-Account Fan-Out: One Producer, Isolated Consumers]]></title>
            <description><![CDATA[A platform default for multi-team AWS orgs: one event, many consumers, each in its own account with its own SQS and DLQ, and fan-out in the event bus layer.]]></description>
            <link>https://sph.sh/en/posts/event-fanout-isolated-consumer-accounts/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/event-fanout-isolated-consumer-accounts/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[eventbridge]]></category>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[multi-account]]></category>
            <category><![CDATA[sqs]]></category>
            <category><![CDATA[dynamodb-streams]]></category>
            <category><![CDATA[cross-account]]></category>
            <category><![CDATA[platform-engineering]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A producer service collects new downstream consumers like barnacles. Every new team that wants events adds another if enabled(x) publish(x) branch, and every new consumer needs a producer deploy. The branches are a junction box pretending to be application code, and the blast radius of any mistake sits in the same account as payments.</p>
<p>The default that removes those branches: one event, many consumers, each in its own AWS account with its own SQS and DLQ. Rules on the event bus carry the fan-out, so the producer's code stays out of it. Audit, Customer Center, Marketing and every consumer after them get the same topology. A different backbone earns its place only when ordering, replay, or raw throughput demands it.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[wasmCloud and NATS: Making the Event Bus Portable]]></title>
            <description><![CDATA[An exploration thesis: vendor lock-in in event-driven systems lives in the bus topology, not the runtime, and wasmCloud plus NATS make the bus portable.]]></description>
            <link>https://sph.sh/en/posts/wasmcloud-nats-event-bus-portability/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/wasmcloud-nats-event-bus-portability/</guid>
            <category><![CDATA[wasmcloud]]></category>
            <category><![CDATA[nats]]></category>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[webassembly]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[aws-eventbridge]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In an event-driven system, vendor lock-in lives in the bus topology rather than in the runtime. The runtime is replaceable in weeks; the topology is replaceable in quarters. Serverless lock-in debates mostly skip that split and compare Lambda against Cloud Functions or Workers against Containers, with the event bus filed under plumbing.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Handling Long-Running API Requests in Web and Mobile Apps]]></title>
            <description><![CDATA[One default shape for long-running work across a browser SPA and a mobile app, with the cases where it should be overridden.]]></description>
            <link>https://sph.sh/en/posts/async-api-patterns-web-mobile/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/async-api-patterns-web-mobile/</guid>
            <category><![CDATA[api-design]]></category>
            <category><![CDATA[websockets]]></category>
            <category><![CDATA[server-sent-events]]></category>
            <category><![CDATA[webhooks]]></category>
            <category><![CDATA[queues]]></category>
            <category><![CDATA[resilience]]></category>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[http]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Where Synchronous Endpoints Break</strong></p>
<p>Most product APIs begin with plain synchronous request and response. It works for login, for reading a user record, for a simple search. Then one endpoint needs to do real work.</p>
<p>Consider a payment authorization that takes six to ten seconds. A spinner that long drops checkout conversion; users retry the button, and without an idempotency layer the backend accepts a duplicate charge.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[One AI Coding Config for Claude Code, Cursor, Copilot, and Codex]]></title>
            <description><![CDATA[A practical repo layout that keeps Claude Code, Codex, Copilot, Cursor, and OpenCode reading the same rules, with honest notes on where portability breaks.]]></description>
            <link>https://sph.sh/en/posts/model-agnostic-ai-coding-setup/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/model-agnostic-ai-coding-setup/</guid>
            <category><![CDATA[ai-coding]]></category>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[github-copilot]]></category>
            <category><![CDATA[cursor]]></category>
            <category><![CDATA[opencode]]></category>
            <category><![CDATA[mcp]]></category>
            <category><![CDATA[developer-workflow]]></category>
            <category><![CDATA[agents-md]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Teams rarely converge on a single AI coding tool; a repo that only works with one of Claude Code, Codex, Copilot, Cursor, or OpenCode loses every contributor who picked a different tool. The layout below keeps one file authoritative: AGENTS.md as the single source of truth, symlinks or @-imports for every other tool's expected filename, and MCP for cross-tool tooling. It breaks in three places: skills, slash commands, and Windows symlink traps.</p>
<p><strong>The Problem</strong></p>
<p>A team adopts Claude Code, then a new hire prefers Cursor. The CLAUDE.md at the repo root sits quiet while Cursor reads .cursor/rules/, so someone hand-copies the rules. Later the same rule lands in AGENTS.md with slightly different wording. Three files drift, and nobody owns the sync.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Optimistic UI vs Decoupled Flows: UX Patterns for Async Backends]]></title>
            <description><![CDATA[A pragmatic guide for designers working with async backends: three interaction patterns, when to use each, and four anti-patterns to push back against.]]></description>
            <link>https://sph.sh/en/posts/ux-event-based-backends-optimistic-to-decoupled/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/ux-event-based-backends-optimistic-to-decoupled/</guid>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[state-management]]></category>
            <category><![CDATA[patterns]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[mobile]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>An event-based backend moves work out of the request/response cycle: the client submits, the server accepts and enqueues, and the state change happens some time later. A spinner signals that the system is actively producing the result for this session; but in an event-based flow there is no in-progress work on the client to wait on. The UI conflates two distinct states (the backend has queued the work versus the backend is computing the result right now), and those two states carry different promises to the user.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Idempotency: A Beginner's Guide to Safe Retries in APIs]]></title>
            <description><![CDATA[A practical introduction to idempotency for developers building APIs, payments, and message consumers, covering HTTP semantics, idempotency keys, and upserts.]]></description>
            <link>https://sph.sh/en/posts/idempotency-beginners-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/idempotency-beginners-guide/</guid>
            <category><![CDATA[idempotency]]></category>
            <category><![CDATA[api-design]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[http]]></category>
            <category><![CDATA[reliability]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Idempotency is the property that makes running the same operation multiple times produce the same result as running it once. It is load-bearing for any API that handles money, side effects, or distributed work. The underlying transport (HTTP, mobile radios, message brokers) retries on failure regardless of intent, and a non-idempotent endpoint turns every retry into a duplicate write: double charges, duplicate orders, jobs fulfilled twice.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mobile Ad Attribution for Subscription Apps: SKAN 4 and AdAttributionKit]]></title>
            <description><![CDATA[An engineering guide to connecting ad clicks to paid subscriptions under SKAN 4, AdAttributionKit, and post-ATT privacy, with event taxonomy and reconciliation.]]></description>
            <link>https://sph.sh/en/posts/mobile-ad-attribution-subscription-conversion/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/mobile-ad-attribution-subscription-conversion/</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[skadnetwork]]></category>
            <category><![CDATA[adattributionkit]]></category>
            <category><![CDATA[revenuecat]]></category>
            <category><![CDATA[storekit-2]]></category>
            <category><![CDATA[meta-capi]]></category>
            <category><![CDATA[attribution]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Subscription app attribution has to connect an ad click on Meta, Google, or TikTok to a validated paid subscription event in the data warehouse. The install is only an intermediate signal on the way there, and SKAN 4 together with ATT make the click-to-install link much harder to observe than the install-to-subscription link. A single pipeline has to reconcile three independently clocked sources (SKAN postbacks, mobile app events, payment-provider receipts) so marketing, finance, and product can work off the same numbers.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[RevenueCat Alternatives in 2026: Choosing a Subscription Platform for Mobile Apps]]></title>
            <description><![CDATA[A decision framework for picking between RevenueCat, Adapty, Qonversion, Apphud, Chargebee, and Stripe Billing in 2026, with pricing math and DMA impact.]]></description>
            <link>https://sph.sh/en/posts/revenuecat-alternatives-comparison-2026/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/revenuecat-alternatives-comparison-2026/</guid>
            <category><![CDATA[revenuecat]]></category>
            <category><![CDATA[adapty]]></category>
            <category><![CDATA[qonversion]]></category>
            <category><![CDATA[apphud]]></category>
            <category><![CDATA[stripe]]></category>
            <category><![CDATA[chargebee]]></category>
            <category><![CDATA[subscriptions]]></category>
            <category><![CDATA[paywall]]></category>
            <category><![CDATA[storekit]]></category>
            <category><![CDATA[in-app-purchase]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mobile subscription infrastructure is a layered choice: the SDK and entitlement engine on-device, the billing/catalog/tax layer behind it, and the paywall, experimentation, and analytics tools on top. RevenueCat is the default for the first layer, and it covers the other two well enough for most teams. Its 1% MTR fee on gross revenue changes the math past roughly \$100k MTR, and the 2026 market has alternatives that beat the default on one axis each: Adapty on paywall experimentation, Qonversion on analytics-per-dollar, Apphud on win-back flows, Chargebee and Stripe Billing on cross-platform billing.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Zapier MCP Permission Control: Scoping AI Agent API Access]]></title>
            <description><![CDATA[How Zapier MCP gives AI agents action-level whitelisting, credential isolation, and human-in-the-loop approval, a managed alternative to custom scoped proxies.]]></description>
            <link>https://sph.sh/en/posts/zapier-mcp-api-permission-control/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/zapier-mcp-api-permission-control/</guid>
            <category><![CDATA[mcp]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[ai-agents]]></category>
            <category><![CDATA[ai-integration]]></category>
            <category><![CDATA[api-design]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[automation]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Direct MCP server integrations give AI agents broad, often uncontrolled access to APIs. A previous post demonstrated how to build custom scoped proxies to enforce least-privilege access. Zapier MCP offers a managed alternative: action-level whitelisting, centralized credential management, and human-in-the-loop approval, all without writing proxy code.</p>
<p>For a team wiring an agent into three or more apps with standard operations, that managed layer is the better default. A hand-built proxy earns its maintenance cost only when a single integration needs deep customization or a sub-100ms budget.</p>
<p><strong>Uncontrolled API Permissions in MCP</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mobile IAP & Paywall Strategies - App Store, Play Store, RevenueCat]]></title>
            <description><![CDATA[A practical guide to mobile in-app purchase rules, paywall patterns, and RevenueCat integration with server-side receipt validation and event-driven architecture.]]></description>
            <link>https://sph.sh/en/posts/mobile-iap-paywall-strategies/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/mobile-iap-paywall-strategies/</guid>
            <category><![CDATA[in-app-purchase]]></category>
            <category><![CDATA[revenuecat]]></category>
            <category><![CDATA[paywall]]></category>
            <category><![CDATA[app-store]]></category>
            <category><![CDATA[google-play]]></category>
            <category><![CDATA[mobile-development]]></category>
            <category><![CDATA[payment-systems]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mobile in-app purchase implementation spans two incompatible billing systems (Apple App Store and Google Play), each with distinct commission rules, receipt formats, and server-side validation requirements. Gaps in that validation pipeline allow jailbroken or rooted devices to claim entitlements without payment, and a single rejected paywall flow can block a subscription launch. For a cross-platform app, the default that holds is standard store billing behind a RevenueCat abstraction. Grant entitlements from server-verified state; a client-side check is not enough. On the RevenueCat path that validation runs on RevenueCat's servers, and on a direct store integration it runs on yours.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cross-Platform Subscription Entitlements: Syncing Web, iOS, and Android]]></title>
            <description><![CDATA[How to build a reliable entitlement sync layer that keeps subscription access consistent across web, iOS, and Android with EventBridge and idempotent processing.]]></description>
            <link>https://sph.sh/en/posts/omnichannel-entitlement-sync/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/omnichannel-entitlement-sync/</guid>
            <category><![CDATA[webhooks]]></category>
            <category><![CDATA[idempotency]]></category>
            <category><![CDATA[entitlements]]></category>
            <category><![CDATA[aws-eventbridge]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[payment-systems]]></category>
            <category><![CDATA[subscriptions]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>When a product sells subscriptions across web, iOS, and Android, each platform emits its own subscription events with its own schema, lifecycle, and delivery delay. An entitlement synchronization layer is the piece that turns those heterogeneous events into a single consistent answer to "what can this user access right now?". A naive per-platform handler drifts out of sync within hours; the underlying problem is distributed state, not webhook plumbing.</p>
<p>The design that holds up is a narrow one: normalize every provider event at the ingress point, publish it to an AWS EventBridge bus, and let one idempotent consumer own the DynamoDB entitlement store your application reads. Refunds, family sharing, and grace periods are where that pipeline earns its keep.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Payment Providers & Compliance: Stripe, Adyen, Chargebee, Paddle, PayPal Compared]]></title>
            <description><![CDATA[A practical comparison of payment providers for SaaS: Merchant of Record vs Payment Processor models, PSD2/SCA compliance, VAT, and a provider decision framework.]]></description>
            <link>https://sph.sh/en/posts/payment-providers-comparison-compliance/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/payment-providers-comparison-compliance/</guid>
            <category><![CDATA[stripe]]></category>
            <category><![CDATA[adyen]]></category>
            <category><![CDATA[chargebee]]></category>
            <category><![CDATA[paddle]]></category>
            <category><![CDATA[paypal]]></category>
            <category><![CDATA[payment-systems]]></category>
            <category><![CDATA[compliance]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Accepting payments globally involves three layers that do not behave the same way: transaction processing, tax compliance, and legal responsibility for the sale. A SaaS team that picks a provider by transaction fee alone usually discovers the other two layers only when a VAT audit, a PSD2/SCA reject rate, or a chargeback policy exposes them. The choice is really a decision about which layers the team operates and which ones it outsources.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Subscription Lifecycle Management: Upgrades, Dunning, and Fraud Detection]]></title>
            <description><![CDATA[A practical guide to subscription state machines, proration strategies, dunning management, and fraud detection patterns with Stripe webhooks and AWS EventBridge.]]></description>
            <link>https://sph.sh/en/posts/subscription-lifecycle-management/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/subscription-lifecycle-management/</guid>
            <category><![CDATA[subscriptions]]></category>
            <category><![CDATA[stripe]]></category>
            <category><![CDATA[fraud-detection]]></category>
            <category><![CDATA[dunning]]></category>
            <category><![CDATA[churn]]></category>
            <category><![CDATA[payment-systems]]></category>
            <category><![CDATA[aws-eventbridge]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Subscription billing has a short happy path and a long tail of edge cases that silently cost revenue. A naive proration step double-charges upgrades; a default retry policy burns through cards during dunning; missing fraud signals land as chargebacks. Subscriptions are not single transactions; they are state machines, and every billing event is a transition that can fire, fail, or repeat.</p>
<p>Model the subscription as a state machine first, then hang everything else off it. Proration rules, dunning cadence, fraud checks, and entitlement flips all become transitions with a defined owner. On Stripe that means one webhook handler that verifies, deduplicates, and publishes to EventBridge, with the business logic running in the consumers behind the bus.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Claude Code Skills and Context Window Bloat: A Token Budget Guide]]></title>
            <description><![CDATA[Copying Claude Code configurations causes context window bloat, degraded tool selection, and mismatched workflows. Build setups intentionally with token budget math.]]></description>
            <link>https://sph.sh/en/posts/why-copying-claude-code-skills-doesnt-work/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/why-copying-claude-code-skills-doesnt-work/</guid>
            <category><![CDATA[developer-experience]]></category>
            <category><![CDATA[ai-tools]]></category>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[best-practices]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 23 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The Claude Code ecosystem now includes 500+ community skills, 1,200+ agent skills, and dozens of MCP server directories. Cloning someone's "awesome" setup is tempting, and it is the wrong default. Copied configurations create three compounding problems: context window bloat leaves less room for actual code, tool sprawl degrades the model's ability to pick the right tool, and the copied workflows do not match your codebase.</p>
<p>The setup that works runs the other way. Start with zero MCP servers, add one only when a repeated friction point justifies it, and keep total configuration overhead under 20% of the context window. The token math below shows what a copied setup costs, and the benchmarks show why the model gets worse as the tool list grows.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Cognito + Verified Permissions for SaaS Authorization]]></title>
            <description><![CDATA[Build SaaS authorization with AWS Cognito and Verified Permissions, covering Cedar policies, multi-tenant patterns, JWT flow, and cost in TypeScript.]]></description>
            <link>https://sph.sh/en/posts/cognito-avp-saas-authorization/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/cognito-avp-saas-authorization/</guid>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[cognito]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[saas]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Authentication is the easy half of a B2B SaaS build on AWS. Cognito signs users in and hands back a JWT, but a valid token says nothing about whether tenant A may read tenant B's documents. AWS Verified Permissions (AVP) is the managed service that answers that second question, and the default worth starting from is the pool model: one Cognito User Pool, one AVP policy store, and a tenant check inside every Cedar policy.</p>
<p>Three decisions carry most of the weight: which claims the Pre Token Generation trigger writes into the access token, whether tenant isolation lives in Cedar policy or in separate infrastructure per tenant, and which AVP API each call path uses. Cedar policy shape, TypeScript wiring, and the monthly bill all follow from those three.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[OpenFGA vs SpiceDB vs Cerbos vs OPA vs AWS Verified Permissions: Which Authorization Platform to Choose]]></title>
            <description><![CDATA[A vendor-neutral evaluation of AWS Verified Permissions, SpiceDB, OpenFGA, Cerbos, and OPA, with architecture patterns, cost analysis, and a decision framework.]]></description>
            <link>https://sph.sh/en/posts/external-authorization-management-systems/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/external-authorization-management-systems/</guid>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[opa]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Building custom authorization works for many applications. Distributed systems increasingly benefit from dedicated authorization infrastructure that handles policy evaluation, relationship management, and fine-grained access control as a service. A reasonable default: keep authorization in an embedded library while it stays inside one service, and adopt an external engine once it crosses a service boundary.</p>
<p>The choice of engine follows from your dominant model. Cedar and AWS Verified Permissions suit attribute-driven decisions on AWS. SpiceDB and OpenFGA suit access that follows relationships between users and resources. Cost, operational overhead, and policy language lock-in decide the rest.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cedar vs Rego vs OpenFGA: Policy Language Comparison]]></title>
            <description><![CDATA[A deep comparison of Cedar, Rego, OpenFGA DSL, and Cerbos YAML/CEL policy languages: syntax, performance, formal verification, tooling, and TypeScript integration.]]></description>
            <link>https://sph.sh/en/posts/policy-language-comparison-cedar-rego-openfga/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/policy-language-comparison-cedar-rego-openfga/</guid>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[opa]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The policy language you choose for authorization shapes the rest of your security architecture: it decides how policies are written, tested, deployed, and audited. Switching later means rewriting all of them, so the choice is worth making deliberately.</p>
<p>Four languages cover most of the field: Cedar, Rego, OpenFGA DSL, and Cerbos YAML/CEL. Two of them work as defaults. When access is fundamentally about relationships ("who has what relationship to what"), OpenFGA DSL is the starting point. When it is about attributes and you want machine-checked guarantees over the whole policy set, Cedar is. Rego and Cerbos answer narrower constraints: one language across infrastructure and application policy, or the shortest path to policies running in production.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SpiceDB vs Auth0 FGA: Relationship-Based Authorization Compared]]></title>
            <description><![CDATA[A deep technical comparison of SpiceDB and Auth0 FGA (OpenFGA), two Zanzibar-inspired systems with different trade-offs in schema, consistency, deployment, and scale.]]></description>
            <link>https://sph.sh/en/posts/spicedb-vs-auth0-fga/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/spicedb-vs-auth0-fga/</guid>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>SpiceDB and Auth0 FGA are the two leading Zanzibar-inspired authorization systems, but they make fundamentally different trade-offs. SpiceDB offers a gRPC-first, self-hosted (or managed via Authzed) engine with strong consistency guarantees through ZedTokens. Auth0 FGA provides a REST-first, fully managed service built on OpenFGA with a different consistency model.</p>
<p>For most teams the default is Auth0 FGA, or self-hosted OpenFGA when the infrastructure budget is zero. Two requirements flip that default to SpiceDB: permission revocation that must take effect on the very next read, and authorization data that cannot leave infrastructure you control. Schema syntax, API ergonomics, and published scale ceilings matter less than those two.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Prioritize Customer Feedback: Triage, RICE, and ICE]]></title>
            <description><![CDATA[A practical operational frame for triage, discovery, and prioritization when every channel shouts at once.]]></description>
            <link>https://sph.sh/en/posts/customer-feedback-product-value/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/customer-feedback-product-value/</guid>
            <category><![CDATA[customer-feedback]]></category>
            <category><![CDATA[product-management]]></category>
            <category><![CDATA[prioritization]]></category>
            <category><![CDATA[user-research]]></category>
            <category><![CDATA[product-discovery]]></category>
            <category><![CDATA[voice-of-customer]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 19 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Customer feedback rarely arrives as a clean problem statement. It lands as Slack threads, support macros, sales notes, star ratings, and one-line feature requests. The hard part is not collecting it. The hard part is <strong>turning that stream into decisions</strong> without letting the loudest voice or the biggest logo own the roadmap by default.</p>
<p>The loop that survives contact with a real team is short: intake, triage, discovery when the job is unclear, delivery, and a closed loop back to the customer. Each step has to hold up against support SLAs, engineering queues, and metrics that are never as clean as the dashboard suggests.</p>
<p><strong>Why feedback feels overwhelming</strong></p>
<p>Several patterns show up again and again across teams:</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Authentication vs Authorization: Fundamentals and Why Permissions Break]]></title>
            <description><![CDATA[Authentication vs authorization, common permission pitfalls, the fail-closed principle, and the goals every permission system should meet.]]></description>
            <link>https://sph.sh/en/posts/scalable-permission-systems-101-authorization-fundamentals/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/scalable-permission-systems-101-authorization-fundamentals/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Broken access control is the number one vulnerability in the OWASP Top 10 (2021, confirmed in the 2025 update). Most permission bugs trace back to an architecture that makes checks easy to forget and hard to keep consistent. As a codebase grows, so does the number of places where a check can quietly go missing.</p>
<p>The default worth adopting from the first commit is a single permission-aware layer that every data access passes through, one that denies access whenever a check cannot complete. Getting there starts with a distinction that plenty of codebases blur.</p>
<p><strong>Authentication vs Authorization</strong></p>
<p>These two concepts are often bundled under the single word "auth," which leads to confusion. They are fundamentally different concerns.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Centralizing Authorization with a Service Layer]]></title>
            <description><![CDATA[Refactor scattered permission checks into a centralized service layer, add Next.js middleware guards, and build a defense-in-depth authorization architecture.]]></description>
            <link>https://sph.sh/en/posts/scalable-permission-systems-102-service-layer-centralization/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/scalable-permission-systems-102-service-layer-centralization/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In Post 101, we diagnosed the root problems with scattered permission checks: inconsistent logic, security gaps, and maintenance overhead. The first fix is the service layer pattern: all authorization logic moves into a single layer between your application code and the database.</p>
<p>Next.js middleware for route protection and guards at several levels build on top of that layer. Together they form a defense-in-depth architecture: a permission rule is written once and enforced on every path to the data.</p>
<p><strong>The Root Cause of Scattered Checks</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Role-Based Access Control: Type-Safe RBAC in TypeScript]]></title>
            <description><![CDATA[Build a type-safe RBAC system in TypeScript, create a unified can() function, sync permissions across UI and backend, and learn when RBAC reaches its limits.]]></description>
            <link>https://sph.sh/en/posts/scalable-permission-systems-103-role-based-access-control/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/scalable-permission-systems-103-role-based-access-control/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[rbac]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Post 102 centralized authorization in the service layer, solving the architectural question of <em>where</em> permission checks happen. But the permission rules inside the service layer are still hardcoded if/else chains: if (session.role === 'admin') return true. Adding a new role means modifying every helper function across every service file.</p>
<p>RBAC (Role-Based Access Control) replaces those chains. Ferraiolo and Kuhn formalized the model in 1992, and NIST standardized it as INCITS 359-2004. A single, type-safe can() function takes over every hardcoded check. It runs on both server and client, closing the permission logic duplication that Post 102 left open.</p>
<p><strong>What Is RBAC?</strong></p>
<p><strong>The NIST Model</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Attribute-Based Access Control in TypeScript: Building an ABAC Policy Engine]]></title>
            <description><![CDATA[Build an ABAC policy engine in TypeScript with the builder pattern, conditional permissions, and type-safe policy evaluation that replaces RBAC's limitations.]]></description>
            <link>https://sph.sh/en/posts/scalable-permission-systems-104-attribute-based-access-control/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/scalable-permission-systems-104-attribute-based-access-control/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[abac]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Post 103 built type-safe RBAC with a can(role, resource, action) function and a declarative permission matrix. Adding a role stays a one-line change, and server and client share a single source of truth. But contextual decisions (ownership, department scoping, document status) stay outside that matrix as custom helper functions. canModifyDocument, canEditInDepartment, and canPublishInReviewStatus proliferate alongside can(), each an if/else chain the type system cannot verify.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Advanced ABAC: Field-Level Permissions and DB Integration]]></title>
            <description><![CDATA[Extend ABAC with environment-based rules, field-level read and write permissions, and automatic database query filtering that eliminates duplicate permission logic.]]></description>
            <link>https://sph.sh/en/posts/scalable-permission-systems-105-advanced-abac-field-level-db/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/scalable-permission-systems-105-advanced-abac-field-level-db/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[abac]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Post 104 built a type-safe ABAC policy engine with a builder pattern. The can(user, action, resource, data?) function evaluates subject, resource, and action attributes through declarative conditions. Ownership, department scoping, and resource status became policy rules; the scattered helper functions are gone. Two gaps are left, and both close by extending that builder rather than by adding checks around it.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Multi-Tenant Authorization in TypeScript: CASL vs Custom ABAC]]></title>
            <description><![CDATA[Add multi-tenant isolation to your permission system, evaluate CASL as a library alternative, and use decision frameworks to choose the right authorization architecture.]]></description>
            <link>https://sph.sh/en/posts/scalable-permission-systems-106-multi-tenancy-libraries-decisions/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/scalable-permission-systems-106-multi-tenancy-libraries-decisions/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[abac]]></category>
            <category><![CDATA[casl]]></category>
            <category><![CDATA[multi-tenancy]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Post 101 established seven goals for a permission system and exposed the scattered-check anti-pattern. Post 102 centralized authorization inside a service layer. Post 103 added type-safe RBAC. Post 104 replaced the role-permission matrix with an ABAC policy engine. Post 105 extended ABAC with environment rules, field-level read/write permissions, and database query filtering.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MCP vs Direct API Access for AI Agents: When to Skip the MCP Layer]]></title>
            <description><![CDATA[Why production teams replace broad MCP access with scoped API proxies. Atlassian, Google Workspace, and Notion via FastAPI proxy, CLI wrapper, and n8n.]]></description>
            <link>https://sph.sh/en/posts/skip-mcp-layer-scoped-api-access/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/skip-mcp-layer-scoped-api-access/</guid>
            <category><![CDATA[mcp]]></category>
            <category><![CDATA[api-design]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[ai-agents]]></category>
            <category><![CDATA[ai-integration]]></category>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Atlassian's Rovo MCP Server connects AI agents to Jira and Confluence in a few minutes, and it hands the agent everything the authenticated user can already see. In an organization with dozens of projects and spaces, that means no server-side project scoping, tool schemas that consume the context window before any work starts, and all-or-nothing exposure of Jira, Confluence, and Compass operations at once.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Control Tower Multi-Account Strategy: From Landing Zone to Enterprise Governance]]></title>
            <description><![CDATA[A practical guide to AWS Control Tower multi-account strategy: OU structure, SCPs, RCPs, Account Factory for Terraform, IAM Identity Center, and security.]]></description>
            <link>https://sph.sh/en/posts/aws-control-tower-multi-account-strategy/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-control-tower-multi-account-strategy/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-control-tower]]></category>
            <category><![CDATA[multi-account]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[iam]]></category>
            <category><![CDATA[scp]]></category>
            <category><![CDATA[landing-zone]]></category>
            <category><![CDATA[governance]]></category>
            <category><![CDATA[terraform]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 06 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AWS Control Tower provides the foundation for a well-governed, multi-account AWS environment. The setup wizard is the easy part; the decisions after it are not. Once the landing zone is live, the shape of the OU tree, the guardrail rollout order, account provisioning, and cross-account access are all still open.</p>
<p>Four defaults hold for most organizations: align OU boundaries with policy boundaries, enable detective controls before preventive ones, centralize logging on day one, and reach for Account Factory for Terraform only when the team already runs Terraform. Everything that follows is about the trade-offs behind those defaults and the conditions that justify overriding them.</p>
<p><strong>Why a Multi-Account Strategy Matters</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Salary, Impact, or Satisfaction? The Vocation Question Every Engineer Faces]]></title>
            <description><![CDATA[Why the salary-vs-impact-vs-satisfaction debate is a false trichotomy, and what your answer reveals about your vocational development stage.]]></description>
            <link>https://sph.sh/en/posts/salary-impact-satisfaction-vocation/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/salary-impact-satisfaction-vocation/</guid>
            <category><![CDATA[career-growth]]></category>
            <category><![CDATA[job-satisfaction]]></category>
            <category><![CDATA[burnout]]></category>
            <category><![CDATA[developer-experience]]></category>
            <category><![CDATA[ikigai]]></category>
            <category><![CDATA[self-determination-theory]]></category>
            <category><![CDATA[career-planning]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 04 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The salary-versus-impact-versus-satisfaction debate is a fixture of engineering career forums, and the framing is where the confusion starts. Salary, impact, and satisfaction describe the dominant need at three different stages of vocational development. Treating them as competing priorities turns a sequencing question into a loyalty test, and the decisions that follow tend to misfire.</p>
<p>Organizational psychology has already mapped those stages. Wrzesniewski's work-orientation research, Self-Determination Theory, and Dan Koe's Vocation quadrant converge on the same three-step progression. Knowing which step you occupy tells you which dimension deserves attention now, and which one can wait.</p>
<p><strong>The Three Camps</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Org-Level Reusable GitHub Actions Workflows: Architecture, Security, and Adoption]]></title>
            <description><![CDATA[A practical guide to building an org-level shared GitHub Actions platform: architecture decisions, security governance, adoption, and 7 costly mistakes.]]></description>
            <link>https://sph.sh/en/posts/github-actions-platform-scale/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/github-actions-platform-scale/</guid>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[platform-engineering]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[governance]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[best-practices]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 01 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>GitHub Actions is deceptively simple to get started with. Copy a YAML file, add a few steps, and the pipeline runs. That simplicity stops scaling once dozens of repositories, each maintained by a different team, carry their own flavor of build, test, and deploy. The result is familiar at mid-to-large engineering organizations: hundreds of workflow files with subtle differences, inconsistent security practices, build times that vary wildly, and a support backlog where platform engineers answer "how do I do X in GitHub Actions?" instead of building platform capabilities.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sentry Integration with React Native Expo: A Practical Quick Guide]]></title>
            <description><![CDATA[Step-by-step guide to adding Sentry to a React Native Expo app: SDK setup, Expo Router instrumentation, session replay, and source maps for EAS.]]></description>
            <link>https://sph.sh/en/posts/sentry-react-native-expo-setup/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/sentry-react-native-expo-setup/</guid>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[expo]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 16 Feb 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Sentry's setup wizard handles a React Native Expo project in one command, and for most apps the wizard plus a development build is the whole answer. The parts that cost hours come afterwards: Expo Go quietly drops native crashes and session replay, EAS Update never uploads source maps on its own, and an auth token left in app.json ships to every device that installs the app.</p>
<p>The configuration below targets Expo SDK 50 or later with @sentry/react-native. The older sentry-expo package was deprecated alongside SDK 50, so new projects should skip it.</p>
<p><strong>Prerequisites</strong></p>
<p>Before starting, you need:</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What Is DevRel? Developer Relations Role, Skills, and Career Path]]></title>
            <description><![CDATA[An analysis of the DevRel role, how it differs from marketing and sales engineering, the skills it needs, and when companies should invest in it.]]></description>
            <link>https://sph.sh/en/posts/devrel-role-analysis/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/devrel-role-analysis/</guid>
            <category><![CDATA[career]]></category>
            <category><![CDATA[engineering-roles]]></category>
            <category><![CDATA[developer-relations]]></category>
            <category><![CDATA[community-building]]></category>
            <category><![CDATA[tech-careers]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 04 Feb 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Developer Relations (DevRel) is often misunderstood as "marketing for developers" or "getting paid to travel and speak at conferences." That reading misses the strategic value. DevRel captures developer reality and feeds it back into product development. It also scales developer education past what a documentation team can cover alone. Everything else follows from that definition: who thrives in the role, and when a company should fund one.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[DynamoDB Throttling: Hot Partition Fixes, Write Sharding, and Retry Strategies]]></title>
            <description><![CDATA[Strategies to prevent and handle DynamoDB throttling in Single Table Design: partition key design, write sharding, capacity modes, DAX, and retry patterns.]]></description>
            <link>https://sph.sh/en/posts/dynamodb-rate-limit-strategies/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/dynamodb-rate-limit-strategies/</guid>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[rate-limiting]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[single-table-design]]></category>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[monitoring]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 28 Jan 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>When working with DynamoDB at scale, throttling becomes an inevitable challenge. The ProvisionedThroughputExceededException error often appears despite having adequate table-level capacity, because DynamoDB enforces capacity separately for each partition.</p>
<p>Partition key design is the lever that matters most. Write sharding, capacity modes, DAX, and retry logic each buy headroom on top of that choice, and none of them undoes a low-cardinality key. The practical order in Single Table Design: high-cardinality keys first, sharding for the access patterns that cannot be high-cardinality, then retries and monitoring around what remains.</p>
<p><strong>Understanding DynamoDB's Throttling Mechanism</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MCP Server RBAC, Tool Composition, and Multi-Agent Workflow Patterns]]></title>
            <description><![CDATA[Enterprise patterns for Model Context Protocol: tool composition, multi-agent orchestration, role-based access control, and production observability.]]></description>
            <link>https://sph.sh/en/posts/mcp-advanced-patterns-workflows-rbac/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/mcp-advanced-patterns-workflows-rbac/</guid>
            <category><![CDATA[mcp]]></category>
            <category><![CDATA[ai-integration]]></category>
            <category><![CDATA[rbac]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[enterprise-architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Most MCP material stops at the first working server: register a few tools, connect a client, done. The failure modes start later, when a second team connects to the same server and one of the registered tools can change production.</p>
<p>Six patterns cover that gap: tool annotations, workflow orchestration, RBAC with attribute conditions, progressive authorization, error recovery, and observability. If you adopt only one, make it RBAC backed by audit logging. Permissions and audit trails are the layers that hurt to retrofit once agents are already calling tools, while orchestration can wait until you have two workflows to compare.</p>
<p><strong>The Scaling Challenge</strong></p>
<p>Organizations successfully deploying basic MCP integrations encounter predictable challenges as they sca...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[RAG Data Preparation: The Foundation That Makes or Breaks Your AI System]]></title>
            <description><![CDATA[Comprehensive guide to preparing data for RAG systems covering document parsing, chunking strategies, contextual enrichment, and embedding optimization]]></description>
            <link>https://sph.sh/en/posts/rag-data-preparation/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/rag-data-preparation/</guid>
            <category><![CDATA[rag]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[embeddings]]></category>
            <category><![CDATA[langchain]]></category>
            <category><![CDATA[vector-databases]]></category>
            <category><![CDATA[data-preparation]]></category>
            <category><![CDATA[genai]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Most RAG implementation failures trace back to data preparation, not retrieval architecture. Teams spend weeks tuning retrieval parameters when the real problem is poorly parsed documents or inappropriate chunking. Validated parsing plus recursive splitting at around 512 tokens is the default worth starting from; semantic and parent-child chunking earn their extra cost only once measurements say they do.</p>
<p><strong>The Quality Ceiling of a RAG Pipeline</strong></p>
<p>There is a common pattern in RAG implementations: sophisticated retrieval architectures (hybrid search, reranking, CRAG) that still produce poor results. The root cause is almost always upstream in the data preparation layer.</p>
<p>[code block]</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What Is a Forward Deployed Engineer? FDE vs Solutions Architect and TAM]]></title>
            <description><![CDATA[How the Forward Deployed Engineer role differs from Solutions Architect and Technical Account Manager, and why AI implementation made this hybrid role essential.]]></description>
            <link>https://sph.sh/en/posts/forward-deployed-engineer-role-analysis/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/forward-deployed-engineer-role-analysis/</guid>
            <category><![CDATA[career]]></category>
            <category><![CDATA[engineering-roles]]></category>
            <category><![CDATA[ai-implementation]]></category>
            <category><![CDATA[enterprise-software]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 19 Jan 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A Forward Deployed Engineer (FDE) writes production code inside the customer's infrastructure and stays until that code runs. That is the whole boundary: Solutions Architects, Solutions Engineers, and Technical Account Managers all hand implementation back to the customer, and the FDE keeps it. The title, the compensation band, and the burnout risk all follow from that one difference.</p>
<p>Demand climbed with enterprise AI, because an AI system needs customer context (historical data, workflows, business rules) before it produces anything useful, and a demo environment cannot supply that context. The trade is honest: an FDE-heavy organization buys deep integration at the cost of gross margin, and that trade pays off only while the product category is still new.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[TypeScript AI SDK Comparison: Vercel AI SDK vs OpenAI Agents SDK for Agent Development]]></title>
            <description><![CDATA[A practical comparison of TypeScript AI SDKs for building agents: Vercel AI SDK, OpenAI Agents SDK, and AWS Bedrock, with code examples and decision frameworks.]]></description>
            <link>https://sph.sh/en/posts/typescript-ai-sdk-agent-tooling/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/typescript-ai-sdk-agent-tooling/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[ai-tools]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 19 Jan 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Building an AI agent in TypeScript starts with a boring decision: which SDK carries your tool calls, your streaming, and your provider credentials. Three answers dominate, and each optimizes for something different. Vercel AI SDK trades provider-specific surface for portability, OpenAI Agents SDK builds multi-agent orchestration into the runtime, and direct provider SDKs keep every knob exposed.</p>
<p>For most TypeScript and Next.js codebases the default is Vercel AI SDK. The interesting question is where that default stops paying: heavy multi-agent orchestration, and single-provider services that need a feature no abstraction exposes yet.</p>
<p><strong>The TypeScript AI SDK Landscape</strong></p>
<p>Each of the three places the abstraction at a different level:</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[RAG vs Fine-Tuning vs Off-the-Shelf AI: An Enterprise Decision Framework]]></title>
            <description><![CDATA[A practical 6-level framework for enterprise AI integration: when to use ChatGPT, RAG, MCP agents, or fine-tuning, with a focus on PII and finance compliance.]]></description>
            <link>https://sph.sh/en/posts/ai-integration-levels-enterprise-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/ai-integration-levels-enterprise-guide/</guid>
            <category><![CDATA[ai-integration]]></category>
            <category><![CDATA[enterprise-ai]]></category>
            <category><![CDATA[rag]]></category>
            <category><![CDATA[mcp]]></category>
            <category><![CDATA[fine-tuning]]></category>
            <category><![CDATA[compliance]]></category>
            <category><![CDATA[gdpr]]></category>
            <category><![CDATA[kvkk]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 17 Jan 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Enterprise AI adoption follows a predictable pattern: teams reach for sophisticated architectures before validating simpler ones. A six-level ladder, from direct chat at L1 to fine-tuning at L6, lines those options up against the business need behind them. The default sits lower than most roadmaps assume. L2, a system prompt with uploaded knowledge files, covers more internal use cases than it gets credit for, and you climb only when a measured limitation pushes you up.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI/LLM Glossary: 82 Terms Every Developer Should Know]]></title>
            <description><![CDATA[An implementation-focused glossary for developers navigating the AI/LLM landscape - from tokens to agents, RAG to fine-tuning, with code examples.]]></description>
            <link>https://sph.sh/en/posts/ai-llm-glossary-developer-terms/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/ai-llm-glossary-developer-terms/</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[ai-agents]]></category>
            <category><![CDATA[rag]]></category>
            <category><![CDATA[embeddings]]></category>
            <category><![CDATA[prompt-engineering]]></category>
            <category><![CDATA[fine-tuning]]></category>
            <category><![CDATA[mcp]]></category>
            <category><![CDATA[langchain]]></category>
            <category><![CDATA[openai]]></category>
            <category><![CDATA[anthropic]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 17 Jan 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AI terminology evolves faster than most documentation can keep up with. New terms appear weekly - RAG, RLHF, LoRA, MCP, GGUF - often with inconsistent definitions across different sources. The gap is practical. Vendor materials conflate concepts, and knowing what a term means rarely tells you when to use it.</p>
<p>Every entry pairs a definition with implementation context: what the concept costs in practice, where it is commonly misread, and when to reach for something else. That context is what separates explaining embeddings to a PM from knowing whether embedding your knowledge base is the right call.</p>
<p><strong>Core Concepts</strong></p>
<p><strong>LLM (Large Language Model)</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comparing TypeScript Formatting and Linting Tools: Biome, Oxlint, ESLint, and Prettier]]></title>
            <description><![CDATA[A comparison of modern TypeScript linting and formatting tools - ESLint, Prettier, Biome, and Oxlint - with benchmarks, config examples, and migration tips.]]></description>
            <link>https://sph.sh/en/posts/compare-typescript-formatting-linting-tools/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/compare-typescript-formatting-linting-tools/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[eslint]]></category>
            <category><![CDATA[prettier]]></category>
            <category><![CDATA[biome]]></category>
            <category><![CDATA[oxlint]]></category>
            <category><![CDATA[tooling]]></category>
            <category><![CDATA[linting]]></category>
            <category><![CDATA[formatting]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 13 Jan 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The TypeScript tooling landscape has shifted with Rust-based alternatives entering the scene. For a new project, Biome is the sensible default: a single binary covers linting, formatting, and import organization, and it runs 15-25x faster than ESLint + Prettier. For an existing codebase with a working ESLint setup, the first move is adding Oxlint alongside it; that buys measurable speed without touching the configuration you already trust.</p>
<p>That default has a boundary. Rust-based tools gain speed by giving up ecosystem breadth and type-aware rule coverage: Biome's Biotype covers roughly 75-85% of typescript-eslint rules, and Oxlint's tsgolint ships 43 of them. Where those gaps matter, ESLint + Prettier is still the right answer.</p>
<p><strong>What Traditional Tooling Costs</stron...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Expectation Gap: When Hiring Promises Meet Workplace Reality]]></title>
            <description><![CDATA[An analysis of bait-and-switch hiring, power imbalances, and underemployment, with actionable frameworks for employees to protect themselves and employers to build trust.]]></description>
            <link>https://sph.sh/en/posts/hiring-expectations-workplace-power-dynamics/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/hiring-expectations-workplace-power-dynamics/</guid>
            <category><![CDATA[hiring]]></category>
            <category><![CDATA[career-development]]></category>
            <category><![CDATA[workplace-dynamics]]></category>
            <category><![CDATA[management]]></category>
            <category><![CDATA[employee-experience]]></category>
            <category><![CDATA[organizational-culture]]></category>
            <category><![CDATA[professional-growth]]></category>
            <category><![CDATA[leadership]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 09 Jan 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>What employers promise during recruitment and what employees experience after starting often diverge sharply, and survey data puts that gap at a majority of candidates. The cost lands on both sides: early resignations, disengagement and skill atrophy for employees; replacement spend and a damaged employer brand for companies.</p>
<p>The discipline that closes the gap is the same for both parties. Put the promises in writing before the offer is signed, then check the match against reality at fixed intervals after day one. Neither step depends on goodwill from the other side, which is what makes them work.</p>
<p><strong>The Scale of the Problem</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Post-Acquisition Culture Integration: Why Acquisitions Fail]]></title>
            <description><![CDATA[Why acquirers destroy the value they paid for through cultural absorption, with M&A failure patterns, organizational research, and proven integration strategies.]]></description>
            <link>https://sph.sh/en/posts/post-acquisition-cultural-erosion/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/post-acquisition-cultural-erosion/</guid>
            <category><![CDATA[mergers-acquisitions]]></category>
            <category><![CDATA[organizational-culture]]></category>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[business-strategy]]></category>
            <category><![CDATA[cultural-integration]]></category>
            <category><![CDATA[talent-retention]]></category>
            <category><![CDATA[change-management]]></category>
            <category><![CDATA[industry-insights]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 07 Jan 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Post-acquisition cultural erosion is the gradual loss of the acquired company's distinct capabilities, talent, and decision patterns after the deal closes. It is the single largest source of unrealized M&A value, and it traces to cultural misalignment far more often than to market, product, or financial causes. Cultural erosion is not random drift; it is a systematic process driven by ethnocentric leadership, in-group versus out-group dynamics, and the unconscious bias that funnels decision authority toward the acquirer's existing norms.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Running Bun and Alternative JavaScript Runtimes on AWS Lambda]]></title>
            <description><![CDATA[Run Bun and Deno on AWS Lambda with custom runtimes: performance benchmarks, cost analysis, and production deployment patterns.]]></description>
            <link>https://sph.sh/en/posts/bun-on-lambda-custom-runtimes/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/bun-on-lambda-custom-runtimes/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[bun]]></category>
            <category><![CDATA[deno]]></category>
            <category><![CDATA[custom-runtime]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 26 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AWS Lambda officially supports Node.js, but the platform's custom runtime capability opens the door to alternative JavaScript runtimes like Bun and Deno. Two mechanisms make that work: Lambda Layers and container images. Both give up the initialization tuning AWS applies to its own managed runtimes, and that trade lands on cold starts.</p>
<p>For most Lambda workloads the managed Node.js runtime stays the right default. An alternative runtime earns its slot only when a measured constraint justifies it, and in that case a container image with a pre-warmed Deno cache is the more predictable of the two paths.</p>
<p><strong>The Custom Runtime Question</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Prompt Engineering for Production Systems: A Systematic Engineering Approach]]></title>
            <description><![CDATA[A technical guide to production-grade prompt engineering: systematic design, security, observability, and cost optimization for enterprise LLM apps.]]></description>
            <link>https://sph.sh/en/posts/prompt-engineering-production/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/prompt-engineering-production/</guid>
            <category><![CDATA[prompt-engineering]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[ai-development]]></category>
            <category><![CDATA[production-systems]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[langchain]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 26 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Prompts that behave in a notebook rarely behave in production. The same template drifts under load, leaks its system instructions to a curious user, and quietly multiplies the token bill. Better wording fixes none of that. The reliable default is to treat a prompt as versioned infrastructure: explicit structure, a stored version with metadata, tracing on every call, layered input and output checks, and a token budget.</p>
<p>Five decisions follow from that default: how to structure a prompt so that user data is marked as data rather than instructions, how to version and roll out changes, what to trace, how to defend against injection, and where the cost actually goes.</p>
<p><strong>The Production Gap</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SOLID Principles in JavaScript: Practical Guide with TypeScript and React]]></title>
            <description><![CDATA[How SOLID principles apply to modern JavaScript: practical examples with TypeScript, React hooks, and functional patterns, plus when they're overkill.]]></description>
            <link>https://sph.sh/en/posts/solid-principles-javascript/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/solid-principles-javascript/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[solid-principles]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[clean-code]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 26 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>SOLID principles were formulated for object-oriented programming, but modern JavaScript development looks different: functional patterns, React hooks, dynamic typing. They still hold, with one adaptation. Anchor each principle to a composition boundary (a function, a module, a hook, a component's prop list) instead of a class hierarchy, and all five survive the move.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Edge Computing with AWS: CloudFront Functions vs Lambda@Edge]]></title>
            <description><![CDATA[A technical guide to choosing and implementing AWS edge computing for global apps, with practical examples and cost optimization strategies.]]></description>
            <link>https://sph.sh/en/posts/edge-computing-aws/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/edge-computing-aws/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[cloudfront]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[edge-computing]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 25 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Edge computing moves code execution from centralized data centers to locations near users. AWS CloudFront runs a global edge network and offers two ways to put code on it: CloudFront Functions and Lambda@Edge. Default to CloudFront Functions. They cover header work, redirects, and cache key normalization at a fraction of the price, and Lambda@Edge earns its cost only when a request needs network access, a request body, or origin-side events.</p>
<p><strong>Where Edge Code Runs</strong></p>
<p>Both services cut latency by handling a request at the edge instead of at the origin, but they sit at different points in the request lifecycle and carry different limits.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Amazon Cognito Deep Dive: Beyond Basic Authentication]]></title>
            <description><![CDATA[A technical guide to advanced Amazon Cognito: custom auth flows, federation, multi-tenancy, migration strategies, and production-grade security with CDK.]]></description>
            <link>https://sph.sh/en/posts/cognito-deep-dive/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/cognito-deep-dive/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[cognito]]></category>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[multi-tenancy]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[federation]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 24 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Amazon Cognito covers sign-up and sign-in with very little configuration. Production systems ask for more: tenant context carried inside the token, corporate SSO, a migration path off an existing provider, and a backup plan for a directory that AWS does not replicate across regions. The service's sharp edges show up at exactly those points.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Learning Effect: A Practical Adoption Guide for TypeScript Developers]]></title>
            <description><![CDATA[A practical guide to learning Effect incrementally and integrating it with AWS Lambda, with real code examples, common pitfalls, and production patterns.]]></description>
            <link>https://sph.sh/en/posts/learning-effect-adoption-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/learning-effect-adoption-guide/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[effect]]></category>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[functional-programming]]></category>
            <category><![CDATA[dependency-injection]]></category>
            <category><![CDATA[error-handling]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 23 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Effect brings a functional effect system to TypeScript: typed errors, dependency injection, and structured concurrency, all enforced by the compiler. The ecosystem is rich, but the learning curve is steep. Documentation sits scattered across Discord threads, GitHub issues, and blog posts of varying age, so the path from "Hello World" to code you would put behind a production Lambda is not obvious.</p>
<p>The default worth starting from is incremental adoption: write new features and high-complexity modules in Effect, and leave stable Promise-based code alone until you have a reason to touch it. Twelve weeks is a realistic budget for the full path, with two to four of those weeks spent on the basics before service layers, concurrency, and Lambda patterns make sense.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Secrets Manager & Parameter Store: Security Best Practices]]></title>
            <description><![CDATA[A technical guide comparing AWS Secrets Manager and Parameter Store, showing when to use each service with real-world implementation patterns and CDK examples.]]></description>
            <link>https://sph.sh/en/posts/secrets-manager/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/secrets-manager/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[secrets-manager]]></category>
            <category><![CDATA[parameter-store]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[kms]]></category>
            <category><![CDATA[ecs]]></category>
            <category><![CDATA[eks]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 23 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Engineers working with AWS face a common dilemma: choosing between Secrets Manager and Parameter Store for secrets management. Both services store sensitive data, but they serve different purposes and carry different cost structures. The short rule: static config goes to Parameter Store; RDS passwords and anything that has to rotate go to Secrets Manager. Everything after that is a question of cost, cross-account access, and how your workloads read secrets at runtime.</p>
<p><strong>Differences Between the Two Services</strong></p>
<p>Three axes separate the two services: cost, rotation support, and native integrations. Cross-account sharing is a fourth. Secrets Manager resource policies are more flexible there, while Parameter Store goes through RAM (Resource Access Manager).</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[API Versioning Strategies in Practice: From First Release to Sunset]]></title>
            <description><![CDATA[A practical guide to API versioning: URL vs header approaches, breaking changes, Sunset-header deprecation, AWS API Gateway, GraphQL, and contract testing.]]></description>
            <link>https://sph.sh/en/posts/api-versioning/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/api-versioning/</guid>
            <category><![CDATA[api-design]]></category>
            <category><![CDATA[versioning]]></category>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[aws-api-gateway]]></category>
            <category><![CDATA[deprecation]]></category>
            <category><![CDATA[contract-testing]]></category>
            <category><![CDATA[pact]]></category>
            <category><![CDATA[openapi]]></category>
            <category><![CDATA[migration]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 22 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>API versioning is not a URL convention; it is a contract-management problem. A version is a promise to a specific set of clients about a specific set of breaking changes. The strategy behind that promise (URL path, header, content negotiation, or a versioned resource graph) sets the price of every client migration, the time deprecated surface stays deployed, and the amount of infrastructure duplicated during a transition. Most versioning rewrites start the moment a team notices it has been communicating the contract implicitly and now has to make it explicit.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LaunchDarkly vs Unleash vs AWS AppConfig: Feature Flags at Scale]]></title>
            <description><![CDATA[A production guide to feature flags in distributed systems, comparing LaunchDarkly, Unleash, and AWS AppConfig with examples for rollouts and A/B testing.]]></description>
            <link>https://sph.sh/en/posts/feature-flags-scale/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/feature-flags-scale/</guid>
            <category><![CDATA[feature-flags]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[continuous-delivery]]></category>
            <category><![CDATA[trunk-based-development]]></category>
            <category><![CDATA[a-b-testing]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[production]]></category>
            <category><![CDATA[release-management]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Feature flags let you deploy code to production and control feature visibility at runtime. Without that control, deploying features in a distributed system forces a choice between big-bang releases that risk outages and long-lived branches that accumulate merge conflicts. Every deployment becomes an all-or-nothing event tied to a business release schedule.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Playwright vs Cypress: E2E Testing Strategies for Modern Web Apps]]></title>
            <description><![CDATA[Build reliable, maintainable E2E suites with Playwright and Cypress: framework selection, flaky-test prevention, CI/CD integration, and optimization.]]></description>
            <link>https://sph.sh/en/posts/e2e-testing-strategies/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/e2e-testing-strategies/</guid>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[playwright]]></category>
            <category><![CDATA[cypress]]></category>
            <category><![CDATA[e2e]]></category>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 20 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>E2E suites fail for the same handful of reasons in every framework: state shared between tests, selectors tied to markup, and static waits standing in for a real condition. The framework decides how pleasant debugging is. The architecture decides whether the suite still runs six months and one redesign later.</p>
<p>For a new suite, start with Playwright. Free native parallelism, WebKit and Firefox coverage, and the trace viewer remove three recurring sources of pain without a paid tier. Cypress stays a reasonable choice for teams that already work inside its interactive runner. Everything after that decision is architecture: page objects, stable data-testid selectors, API-created test data, and a pyramid that keeps edge cases out of the browser.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Caching Strategies: From Local Memory to Distributed Systems]]></title>
            <description><![CDATA[A practical guide to multi-tier caching: in-memory, Redis, and CDN layers, cache-aside vs write-through, ElastiCache vs MemoryDB, and stampede prevention.]]></description>
            <link>https://sph.sh/en/posts/caching-strategies/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/caching-strategies/</guid>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[elasticache]]></category>
            <category><![CDATA[cloudfront]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 19 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Effective caching is a multi-level problem. The fastest layer is an in-process LRU, the next is a remote cache (Redis or Memcached), then a CDN at the edge. Each layer carries its own invalidation semantics, consistency guarantees, and failure modes. A Redis cluster with a 15% hit rate usually points at the wrong work happening at the wrong level rather than at a bad choice of tool. A thundering herd on a popular expiring key is a stampede-protection problem, and any single-layer cache has it.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Cost Optimization Toolkit - Practical Strategies for Production Workloads]]></title>
            <description><![CDATA[The order that makes AWS cost work stick: visibility with Cost Explorer and Budgets, right-sizing with Compute Optimizer, then commitments.]]></description>
            <link>https://sph.sh/en/posts/aws-cost-optimization/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-cost-optimization/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <category><![CDATA[finops]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[spot-instances]]></category>
            <category><![CDATA[s3]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 18 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AWS cost work has a default order: build visibility, then right-size, then commit. Cost Explorer and Budgets show where the money goes, Compute Optimizer flags the capacity nobody uses, and Savings Plans or Reserved Instances discount whatever survives those two steps. Teams that reverse the order buy a discounted rate for capacity they should have deleted.</p>
<p>The symptoms that send teams looking are usually the same across production accounts: monthly bills that move without a matching traffic change, development resources running 24/7 when they are needed only during working hours, and instances billed for full capacity while their CPUs sit idle.</p>
<p><strong>Five Recurring Cost Drivers</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mozilla SOPS: Encrypting Secrets in Git for GitOps]]></title>
            <description><![CDATA[A practical guide to Mozilla SOPS for encrypting secrets in Git: age encryption, AWS CDK and Lambda patterns, and production-ready serverless security.]]></description>
            <link>https://sph.sh/en/posts/mozilla-sops-gitops-secrets/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/mozilla-sops-gitops-secrets/</guid>
            <category><![CDATA[sops]]></category>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[terraform]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[encryption]]></category>
            <category><![CDATA[aws-kms]]></category>
            <category><![CDATA[age]]></category>
            <category><![CDATA[secrets-management]]></category>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws-sam]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 18 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mozilla SOPS (Secrets OPerationS) answers one question every GitOps setup runs into: how do you keep secrets in version control without publishing them? SOPS encrypts the values inside YAML and JSON files and leaves the keys readable, so the file structure survives code review while passwords and API keys stay ciphertext. Secrets then travel through the same pull request as the code that reads them.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Transactional Outbox Pattern: Reliable Event Publishing in Distributed Systems]]></title>
            <description><![CDATA[Learn how the Transactional Outbox Pattern solves the dual-write problem in distributed systems, with practical implementations using PostgreSQL, DynamoDB, and CDC tools.]]></description>
            <link>https://sph.sh/en/posts/outbox-pattern/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/outbox-pattern/</guid>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[cdc]]></category>
            <category><![CDATA[debezium]]></category>
            <category><![CDATA[patterns]]></category>
            <category><![CDATA[reliability]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 16 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Every event-driven service eventually has to do two things at once: change its own state and tell the rest of the system about it. Without a distributed transaction, a database write and a broker publish cannot be made atomic, so one can fail while the other succeeds. What is left behind is an order nobody reserved stock for, or reserved stock for an order that does not exist.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Understanding Career Levels in Tech - From Entry to Distinguished Engineer]]></title>
            <description><![CDATA[A practical guide to career progression across major tech companies: understanding level equivalencies and making strategic decisions about your engineering path.]]></description>
            <link>https://sph.sh/en/posts/career-levels-tech-companies/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/career-levels-tech-companies/</guid>
            <category><![CDATA[career-growth]]></category>
            <category><![CDATA[engineering-levels]]></category>
            <category><![CDATA[staff-engineer]]></category>
            <category><![CDATA[principal-engineer]]></category>
            <category><![CDATA[big-tech]]></category>
            <category><![CDATA[compensation]]></category>
            <category><![CDATA[career-planning]]></category>
            <category><![CDATA[ic-vs-management]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Career level systems differ enough between companies that titles stop being comparable. A Senior at Amazon is an L6 and a Senior at Google is an L5, and a Staff title at a 40-person startup rarely carries the scope a Staff title carries at Meta. The reliable comparison unit is scope, autonomy, and impact; the number and the label are only how each company records the result. Misreading your equivalent level has a price: you accept a lower band in a new role, or you spend a year building a promotion case for the wrong kind of work.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[RAG Architecture Patterns: Beyond Basic Vector Search]]></title>
            <description><![CDATA[A comprehensive guide to advanced RAG techniques including hybrid search, reranking, GraphRAG, and self-corrective patterns with production AWS implementation examples.]]></description>
            <link>https://sph.sh/en/posts/rag-architecture-patterns/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/rag-architecture-patterns/</guid>
            <category><![CDATA[rag]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[vector-databases]]></category>
            <category><![CDATA[aws-bedrock]]></category>
            <category><![CDATA[opensearch]]></category>
            <category><![CDATA[langchain]]></category>
            <category><![CDATA[knowledge-graphs]]></category>
            <category><![CDATA[embeddings]]></category>
            <category><![CDATA[semantic-search]]></category>
            <category><![CDATA[genai]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Retrieval-Augmented Generation (RAG) systems usually start with plain vector similarity search. That baseline breaks down on three predictable fronts: exact keyword matches, questions whose answer spans several documents, and retrieved context that nobody checked for relevance.</p>
<p>The default worth reaching for first is hybrid search combined with parent-child chunking. Reranking, self-corrective retrieval (CRAG), and knowledge graphs (GraphRAG) sit above it as escalations, added when measurement shows the default falls short. The patterns below map onto AWS Bedrock Knowledge Bases and OpenSearch, and RAGAS metrics decide whether an escalation paid for itself.</p>
<p><strong>The Problem with Basic RAG</strong></p>
<p>Vector similarity alone leaves three specific gaps in production.<...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS AppSync & GraphQL: Building Production-Ready Real-time APIs]]></title>
            <description><![CDATA[Building scalable real-time APIs with AWS AppSync: JavaScript resolvers, subscription filtering, caching strategies, and infrastructure as code patterns.]]></description>
            <link>https://sph.sh/en/posts/appsync-graphql/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/appsync-graphql/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[appsync]]></category>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[real-time]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 14 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AWS AppSync gives you a managed GraphQL endpoint with WebSocket subscriptions, conflict resolution, and direct connections to AWS data sources. For most teams the default that holds is a narrow one: wire resolvers straight to DynamoDB with the JavaScript (APPSYNC_JS) runtime, and reach for Lambda only when a field needs async I/O or logic the runtime cannot express.</p>
<p>That single choice moves latency and cost further than any schema decision does. Subscription filtering, caching layers, single-table versus multi-table modeling, and the CDK wiring around them are all tuning on top of it.</p>
<p><strong>The Hard Parts of Real-time APIs</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI Agent Security: Guardrails and Defense Patterns for Production Systems]]></title>
            <description><![CDATA[Securing AI agents in production with AWS Bedrock Guardrails, defense-in-depth, and patterns that prevent prompt injection, tool misuse, and multi-agent attacks.]]></description>
            <link>https://sph.sh/en/posts/ai-agent-security/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/ai-agent-security/</guid>
            <category><![CDATA[ai-agents]]></category>
            <category><![CDATA[aws-bedrock]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[prompt-injection]]></category>
            <category><![CDATA[guardrails]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[architecture-patterns]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 13 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AI agents that autonomously invoke tools and call external APIs introduce attack surfaces that traditional application security models were not designed to handle. A single prompt injection or a misconfigured tool permission can escalate into data exfiltration, unauthorized actions, or runaway cost.</p>
<p>The default that holds up in production is layered enforcement outside the model: a managed guardrail on every invocation, an explicit authorization check on every tool call, validation on every output, and a hard token budget behind all of it. Prompt-level instructions are the weakest of those layers, so nothing that matters should depend on them alone.</p>
<p><strong>The Agent Attack Surface</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Platform Engineering: Building Internal Developer Platforms That Developers Actually Want to Use]]></title>
            <description><![CDATA[A practical guide to building Internal Developer Platforms with golden paths, self-service infrastructure, and product thinking, using Backstage, Port, and AWS.]]></description>
            <link>https://sph.sh/en/posts/platform-engineering-idp/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/platform-engineering-idp/</guid>
            <category><![CDATA[platform-engineering]]></category>
            <category><![CDATA[developer-experience]]></category>
            <category><![CDATA[backstage]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[internal-developer-platform]]></category>
            <category><![CDATA[golden-paths]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>An Internal Developer Platform earns its keep the day an engineer can ship a service without filing a ticket. The default that gets there is narrower than most platform roadmaps assume: one golden path, built with one pilot team, exposed through an API or CLI before any portal UI, and adopted because it is the fastest route rather than because someone mandated it.</p>
<p>Catalog, self-service actions, IaC orchestration, metrics and tooling all hang off that default. Backstage, Port and the AWS building blocks are decisions you take after the first golden path has earned voluntary adoption.</p>
<p><strong>Understanding Platform Engineering</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Contract Testing with Pact - Ensuring API Compatibility in Microservices]]></title>
            <description><![CDATA[A practical guide to consumer-driven contract testing with Pact in TypeScript microservices, catching breaking API changes before deployment.]]></description>
            <link>https://sph.sh/en/posts/contract-testing-pact/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/contract-testing-pact/</guid>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[pact]]></category>
            <category><![CDATA[contract-testing]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[integration-testing]]></category>
            <category><![CDATA[api-compatibility]]></category>
            <category><![CDATA[production]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Consumer-driven contract testing closes the gap between a passing unit test and a working integration. Each consumer declares what it needs from a provider API, the provider verifies it can deliver that, and a deployment gate blocks any release that would break a dependent service. For TypeScript microservices owned by separate teams, Pact behind a Pact Broker with a can-i-deploy gate is the default worth reaching for before growing the end-to-end suite again.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Custom MCP Servers: A Production-Ready Guide]]></title>
            <description><![CDATA[Build, secure, and deploy custom Model Context Protocol servers for internal systems in TypeScript, with authentication, monitoring, and Kubernetes deployment.]]></description>
            <link>https://sph.sh/en/posts/custom-mcp-servers/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/custom-mcp-servers/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[mcp]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 10 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The Model Context Protocol (MCP) has become a common integration surface across major AI providers. Pre-built servers cover public services like GitHub or Slack well. Internal APIs, proprietary databases, and legacy systems have no such server, and a generic one cannot encode your validation rules, security requirements, or compliance needs. For those systems the default is to build your own, and the build is smaller than it looks: a TypeScript project, a few well-scoped tools, and the same production discipline you already apply to any internal service.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SNS/SQS Cross-Account Fan-Out: Building Multi-Account Event Distribution in AWS]]></title>
            <description><![CDATA[Implement secure cross-account event distribution with Amazon SNS and SQS: IAM policies, KMS encryption, AWS CDK, and common production pitfalls.]]></description>
            <link>https://sph.sh/en/posts/sns-sqs-cross-account-fanout/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/sns-sqs-cross-account-fanout/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-sns]]></category>
            <category><![CDATA[aws-sqs]]></category>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 10 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Cross-account SNS/SQS fan-out lets one SNS topic in a publisher account deliver every event to SQS queues owned by other accounts, without either side handing over credentials. Most of the work is permissions: a topic policy, a queue policy, and, once encryption is in play, a KMS key policy that agrees with both.</p>
<p>Three defaults carry the design. Let the queue owner create the subscription, give every encrypted queue a customer-managed KMS key, and filter at the SNS subscription so consumers never pay for messages they would discard. CDK wiring, FIFO ordering, and cross-account dashboards all hang off those three.</p>
<p><strong>What the Pattern Buys</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[FinOps for AI Workloads: Managing LLM Costs in Production]]></title>
            <description><![CDATA[Prompt caching, model routing, token budgets, and semantic caching: how to keep production LLM spend predictable without giving up answer quality.]]></description>
            <link>https://sph.sh/en/posts/finops-ai-workloads/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/finops-ai-workloads/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[finops]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <category><![CDATA[bedrock]]></category>
            <category><![CDATA[openai]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 09 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Running large language models in production breaks the cost model most cloud teams already trust. Compute-hour billing is predictable, and token billing is not. The same feature can cost a fraction of a cent or several dollars per call, depending on how much context the prompt carries, which model answers, and how many tool calls the agent makes before it stops.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI Agent Architecture Patterns: ReAct, Plan-and-Execute, Multi-Agent]]></title>
            <description><![CDATA[The architectural evolution from rule-based chatbots to autonomous AI agents, covering ReAct, Plan-and-Execute, and multi-agent patterns in TypeScript.]]></description>
            <link>https://sph.sh/en/posts/chatbots-to-autonomous-agents/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/chatbots-to-autonomous-agents/</guid>
            <category><![CDATA[ai-agents]]></category>
            <category><![CDATA[chatbots]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[agentic-ai]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[design-patterns]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A chatbot classifies intent and replays a scripted flow. An agent runs a reasoning loop, selects tools at runtime, and carries memory between tasks. The gap between them is not a capability upgrade you can bolt on; it changes how state, control flow, and failure handling are designed.</p>
<p>The default worth starting from is a single ReAct agent with hybrid memory. It handles dynamic tool use without the coordination overhead of a multi-agent system, and it leaves one control loop to debug. Plan-and-Execute earns its place when the task decomposes cleanly and cost per task is the binding constraint. Multiple specialized agents earn theirs only when the domains are genuinely separate and a wrong answer is expensive enough to pay for the coordination layer.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Saga Pattern for Distributed Transactions: Maintaining Consistency Without ACID]]></title>
            <description><![CDATA[Implement the Saga pattern across microservices with AWS Step Functions and EventBridge, covering idempotency, compensation logic, and production patterns.]]></description>
            <link>https://sph.sh/en/posts/saga-pattern-distributed-transactions/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/saga-pattern-distributed-transactions/</guid>
            <category><![CDATA[saga-pattern]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[aws-step-functions]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[eventbridge]]></category>
            <category><![CDATA[eventual-consistency]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 07 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Microservices split a single business transaction across several databases, and ACID guarantees stop at the service boundary. The Saga pattern fills that gap with local transactions plus compensating transactions: each committed step can be undone when a later step fails, and the system settles into eventual consistency.</p>
<p>Orchestration with AWS Step Functions is the safer default for order-shaped flows, because saga state and failure paths stay visible in one place. EventBridge choreography is cheaper and looser, and it earns its slot when three or four autonomous services already exchange domain events. Whichever coordination style you pick, idempotent steps, reverse-order compensation, and semantic locking are what keep the pattern standing under retries.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Testing Serverless Applications: A Practical Strategy Guide]]></title>
            <description><![CDATA[Build a testing strategy for AWS Lambda, API Gateway, DynamoDB, and Step Functions with practical patterns for fast feedback and reliability.]]></description>
            <link>https://sph.sh/en/posts/testing-serverless-applications/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/testing-serverless-applications/</guid>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[jest]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[aws-sam]]></category>
            <category><![CDATA[localstack]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[step-functions]]></category>
            <category><![CDATA[eventbridge]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[integration-testing]]></category>
            <category><![CDATA[production]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 06 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Serverless applications compress the deploy cycle to minutes, which changes the economics of testing: the bug that used to be caught by a long release process now reaches production before a human reviews it. The testing strategy has to catch what the deploy cadence no longer will. Serverless architecture also breaks local-first testing; Lambda cold starts, IAM permissions, event schemas, and the boundaries between managed services all behave differently locally than in a live account.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Messaging Services: SQS vs SNS vs EventBridge - A Decision Framework]]></title>
            <description><![CDATA[Choose between SQS, SNS, and EventBridge by communication pattern, not features, with working CDK examples and a clear cost analysis.]]></description>
            <link>https://sph.sh/en/posts/aws-messaging-comparison/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-messaging-comparison/</guid>
            <category><![CDATA[aws-sqs]]></category>
            <category><![CDATA[aws-sns]]></category>
            <category><![CDATA[aws-eventbridge]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[messaging]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[serverless]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Choosing between SQS, SNS, and EventBridge is not a feature comparison; it is a communication-model decision. SQS is a point-to-point work queue, SNS is a pub/sub fan-out, and EventBridge is a schema-aware event router with replay. A team that picks by feature list usually discovers the mismatch late: the wrong service model forces a custom layer that rebuilds behaviour the right service provides natively. Most AWS messaging rewrites start there.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Step Functions Deep Dive: Building Resilient Workflow Orchestration]]></title>
            <description><![CDATA[Build production serverless workflows with Step Functions: Standard vs Express, Distributed Map, error handling, and cost optimization with working CDK examples.]]></description>
            <link>https://sph.sh/en/posts/step-functions-deep-dive/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/step-functions-deep-dive/</guid>
            <category><![CDATA[aws-step-functions]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[workflow-orchestration]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AWS Step Functions moves orchestration out of Lambda code and into a state machine, but two decisions shape everything that follows: Standard or Express workflows, and how much error handling to push into the state machine. For high-volume work that finishes in minutes, Express is the right default. Standard earns its higher per-transition price when a workflow runs for days, needs exactly-once semantics, or has to leave an audit trail.</p>
<p>Getting those two decisions right is most of the value. The rest is a small set of patterns that hold up in production: Distributed Map for datasets too large for a regular Map state, Task Token callbacks for human approvals, direct service integrations that skip the Lambda wrapper, and a cost model you can compute before you deploy.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LangChain in Production: Patterns That Work and Anti-Patterns That Don't]]></title>
            <description><![CDATA[Lessons from running LangChain in production: the anti-patterns that cause failures, the patterns that work, with code examples and cost optimization strategies.]]></description>
            <link>https://sph.sh/en/posts/langchain-production-patterns/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/langchain-production-patterns/</guid>
            <category><![CDATA[langchain]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[production]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[ai-agents]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <category><![CDATA[monitoring]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>The Production Gap</strong></p>
<p>Moving LangChain applications from prototype to production reveals a gap between documentation examples and real-world requirements. What works perfectly in development can become costly, slow, or unreliable under production load.</p>
<p>Prototype workloads hide failure modes that only surface at scale: agents that loop on ambiguous inputs, token spend that climbs with every conversation turn, and silent failures that reach you as user complaints. The framework's abstractions accelerate prototyping but obscure the cost, latency, and reliability levers you need under production load.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building an MCP Server in TypeScript: A Production Guide]]></title>
            <description><![CDATA[Learn how MCP standardizes AI tool integration, with TypeScript examples for building servers, managing security, and optimizing performance in production.]]></description>
            <link>https://sph.sh/en/posts/mcp-standard-ai-integration/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/mcp-standard-ai-integration/</guid>
            <category><![CDATA[mcp]]></category>
            <category><![CDATA[ai-integration]]></category>
            <category><![CDATA[claude]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[api-design]]></category>
            <category><![CDATA[llm]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 02 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>The Integration Problem</strong></p>
<p>AI integrations expose a recurring pattern: every new AI model needs custom connections to every data source and tool. The math is brutal; M models multiplied by N tools means M×N custom implementations. Teams routinely spend weeks building bespoke integrations for Slack, GitHub, and databases, only to repeat the entire process when switching AI providers.</p>
<p>Traditional APIs weren't designed for this. REST endpoints expect predictable request patterns, but AI agents generate hundreds of requests per conversation with wildly different latency requirements. GraphQL helps with flexible queries, but lacks built-in support for dynamic tool discovery or session management across multiple invocations.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Production-Ready AI Agents with AWS Bedrock AgentCore]]></title>
            <description><![CDATA[How AWS Bedrock AgentCore solves the infrastructure challenges of deploying agentic AI at scale, with runtime, memory, gateway, and multi-agent coordination.]]></description>
            <link>https://sph.sh/en/posts/bedrock-agentcore-production/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/bedrock-agentcore-production/</guid>
            <category><![CDATA[aws-bedrock]]></category>
            <category><![CDATA[ai-agents]]></category>
            <category><![CDATA[agentic-ai]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[production]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Deploying LangChain or CrewAI agents to production requires session isolation, credential management, memory persistence, and observability: infrastructure that takes months to build correctly from scratch. Without it, agents lack security boundaries between users and have no durable state across sessions, which makes them unsuitable for real workloads.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Aurora vs RDS: When to Choose Amazon Aurora (Architecture and Cost)]]></title>
            <description><![CDATA[A guide to Aurora architecture, I/O cost analysis, and when to choose it over RDS, with migration strategies and real-world decision frameworks.]]></description>
            <link>https://sph.sh/en/posts/amazon-aurora-introduction/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/amazon-aurora-introduction/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aurora]]></category>
            <category><![CDATA[rds]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[mysql]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 29 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Choosing between Amazon Aurora and standard RDS isn't straightforward. Aurora promises 5x MySQL and 3x PostgreSQL performance, automatic storage scaling to 128TB, and 99.99% availability. But it comes with additional complexity and cost that can surprise teams unfamiliar with its I/O pricing model.</p>
<p>The decision comes down to workload characteristics, operational requirements, and cost constraints. For a production MySQL or PostgreSQL workload that needs high availability or more than five read replicas, Aurora Standard is the sensible default. Switch to I/O-Optimized once I/O passes roughly a quarter of the bill, and stay on RDS when the engine is something else, or when the workload is small, predictable, and budget-bound.</p>
<p><strong>What is Amazon Aurora?</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Breaking Through CloudFormation's 500 Resource Barrier: Practical Strategies for Large-Scale Infrastructure]]></title>
            <description><![CDATA[Overcome CloudFormation's 500 resource limit with nested stacks, cross-stack references, SSM Parameter Store, and microstack architecture, shown in TypeScript CDK.]]></description>
            <link>https://sph.sh/en/posts/cloudformation-500-resource-limit-strategies/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/cloudformation-500-resource-limit-strategies/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[cloudformation]]></category>
            <category><![CDATA[infrastructure-as-code]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[nested-stacks]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 18 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>CloudFormation caps every stack at 500 resources, and no service quota request raises it. Serverless applications reach that ceiling faster than most teams expect. A single Lambda function with its role, policy, log group, version, and alarms already costs eight to twelve resources, so sixty functions consume the entire budget before a single table or queue is declared.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[DynamoDB Single-Table Design: A Comprehensive Modeling Guide]]></title>
            <description><![CDATA[Model relationships, choose between GSI and LSI, integrate DAX caching, and avoid hot partitions in production DynamoDB single-table designs with working examples.]]></description>
            <link>https://sph.sh/en/posts/dynamodb-single-table-design-comprehensive-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/dynamodb-single-table-design-comprehensive-guide/</guid>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[nosql]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[database-design]]></category>
            <category><![CDATA[single-table-design]]></category>
            <category><![CDATA[performance-optimization]]></category>
            <category><![CDATA[data-modeling]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 17 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Thinking in terms of relational tables when using DynamoDB causes more problems than it solves. The typical approach (separate tables for Users, Orders, and Products) leads to multiple round-trips, complex application logic, and unpredictable costs.</p>
<p>Single-table design stores multiple entity types in one table using generic partition and sort keys. Instead of fetching a customer from one table and their orders from another, you retrieve everything in a single request. Make it the default when entities are read together, and keep separate tables when they are not. The pattern pays for itself through data locality, not through a lower table count.</p>
<p><strong>Core Principles</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS CDK Project Structure: Service-Based vs Domain-Based Organization]]></title>
            <description><![CDATA[When to use service-based, domain-based, feature-based, or layer-based organization in AWS CDK projects, with decision frameworks and common pitfalls.]]></description>
            <link>https://sph.sh/en/posts/aws-cdk-code-organization-service-vs-domain-based/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-cdk-code-organization-service-vs-domain-based/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[infrastructure-as-code]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[domain-driven-design]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 16 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>An AWS CDK app is a program, so its folder and stack layout is a design decision the framework leaves to you. Skip that decision and the cost shows up later as tight coupling, circular dependencies between stacks, and deploys nobody can run without checking with another team first. The layout that holds up at scale is domain-based organization with a thin foundation layer: everything a business domain owns lives in one folder and one stack, while VPC, security, and monitoring sit in a shared stack beneath them.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Reusable AWS CDK Constructs: Factory Functions, Builders, and Aspects]]></title>
            <description><![CDATA[How factory functions, higher-order functions, and composition turn AWS CDK into a type-safe, reusable infrastructure toolkit that prevents configuration drift.]]></description>
            <link>https://sph.sh/en/posts/aws-cdk-functional-patterns-reusable-configurations/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-cdk-functional-patterns-reusable-configurations/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[infrastructure-as-code]]></category>
            <category><![CDATA[functional-programming]]></category>
            <category><![CDATA[best-practices]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 15 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AWS CDK lets you treat infrastructure as real code. Without shared patterns, though, teams end up with duplicated configurations, inconsistent settings, and deployment failures that a type checker could have caught. Two moves fix most of it: put every recurring resource behind a factory function, and enforce the settings nobody can afford to forget (RemovalPolicy, encryption, log retention) with CDK Aspects. Builders, composers, and custom L3 constructs are extensions of those two, added later.</p>
<p>The trade-off is indirection. Once a Lambda is created by createApiLambda, a reader has to open the factory to see what that function actually gets. That cost is worth paying for resources that repeat and policies that must hold everywhere, and it is not worth paying for a one-off bucket.<...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Choosing IoT Messaging Protocols for Logistics: MQTT, AMQP, ZeroMQ, CoAP, and DDS Compared]]></title>
            <description><![CDATA[A technical comparison of MQTT, AMQP, ZeroMQ, CoAP, and DDS for IoT logistics: fleet tracking, cold chain monitoring, and real-time device communication.]]></description>
            <link>https://sph.sh/en/posts/iot-messaging-protocols-logistics-tracking/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/iot-messaging-protocols-logistics-tracking/</guid>
            <category><![CDATA[mqtt]]></category>
            <category><![CDATA[amqp]]></category>
            <category><![CDATA[zeromq]]></category>
            <category><![CDATA[coap]]></category>
            <category><![CDATA[dds]]></category>
            <category><![CDATA[iot]]></category>
            <category><![CDATA[aws-iot]]></category>
            <category><![CDATA[logistics]]></category>
            <category><![CDATA[fleet-tracking]]></category>
            <category><![CDATA[protocol-comparison]]></category>
            <category><![CDATA[messaging]]></category>
            <category><![CDATA[real-time]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 09 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Protocol choice in an IoT logistics stack sets the ceiling on delivery guarantees, latency, and the monthly bandwidth bill. MQTT is the right default for fleet tracking and cold chain monitoring: it tolerates unreliable cellular links, ships three QoS levels, and has mature brokers and client libraries in every language you are likely to use. AMQP, ZeroMQ, CoAP, and DDS each beat it on one axis, so the useful question is which axis your deployment is constrained by.</p>
<p><strong>Constraints That Drive the Choice</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Behavioral Design Patterns in TypeScript: Observer, Strategy, Command, State]]></title>
            <description><![CDATA[Observer, Strategy, Command, State, and Mediator in TypeScript: which ones the language already covers, and when RxJS, Redux, or XState earn their place.]]></description>
            <link>https://sph.sh/en/posts/behavioral-patterns-reactive-programming/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/behavioral-patterns-reactive-programming/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[rxjs]]></category>
            <category><![CDATA[redux]]></category>
            <category><![CDATA[reactive-programming]]></category>
            <category><![CDATA[state-management]]></category>
            <category><![CDATA[xstate]]></category>
            <category><![CDATA[behavioral-patterns]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Behavioral patterns define how objects communicate and distribute responsibilities. The Gang of Four documented Observer, Strategy, Command, State, and Mediator for C++ and Smalltalk, languages where each one cost a page of boilerplate before it did anything useful. TypeScript changed that arithmetic. First-class functions, closures, and discriminated unions cover several of these patterns outright, and RxJS, Redux, and XState absorbed most of the rest into framework conventions.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creational Design Patterns in TypeScript: Singleton, Factory, Builder, Prototype]]></title>
            <description><![CDATA[How Singleton, Factory, Builder, and Prototype patterns evolved in TypeScript: when ES modules replace singletons and when factory functions beat classes.]]></description>
            <link>https://sph.sh/en/posts/creational-patterns-modern-typescript/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/creational-patterns-modern-typescript/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[creational-patterns]]></category>
            <category><![CDATA[singleton]]></category>
            <category><![CDATA[factory]]></category>
            <category><![CDATA[builder]]></category>
            <category><![CDATA[best-practices]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Object instantiation in TypeScript rarely needs a Gang of Four pattern anymore. The default is the language feature: an ES module for a single shared instance, a factory function for conditional construction, object spread or structuredClone() for copies. Wrapping new in a class hierarchy that a module export already handles adds abstraction, harms testability, and obscures intent.</p>
<p>Singleton, Factory, Builder, and Prototype still earn their place, but the trigger is narrower than the textbooks suggest: shared configuration that many objects must apply consistently, or construction that has to be validated in steps. Everything else is better served by what TypeScript already gives you.</p>
<p><strong>The Singleton Pattern: Anti-Pattern or Right Tool</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Modern React and TypeScript Design Patterns Beyond the Gang of Four]]></title>
            <description><![CDATA[Modern patterns from the JavaScript and TypeScript ecosystems - hooks, compound components, render props, and repositories that solve problems the GoF never faced.]]></description>
            <link>https://sph.sh/en/posts/design-patterns-beyond-gof/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/design-patterns-beyond-gof/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[hooks]]></category>
            <category><![CDATA[react-patterns]]></category>
            <category><![CDATA[compound-components]]></category>
            <category><![CDATA[repository-pattern]]></category>
            <category><![CDATA[modern-patterns]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The Gang of Four documented patterns for C++ and Smalltalk in 1994, before asynchronous programming, component composition, and reactive data flow shaped how applications get written. JavaScript and TypeScript grew their own answers: React Hooks for sharing stateful logic, Compound Components for flexible APIs, the Repository pattern for data access, and ES modules as encapsulation. For TypeScript work on the front end or in Node, one default carries most of the weight: reach for a language or framework feature first, and add a pattern only when it removes a problem you can name.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Structural Design Patterns in React and TypeScript]]></title>
            <description><![CDATA[How Decorator, Adapter, Facade, Composite, and Proxy patterns evolved in React and TypeScript: when HOCs give way to hooks and how adapters isolate third-party APIs.]]></description>
            <link>https://sph.sh/en/posts/structural-patterns-component-composition/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/structural-patterns-component-composition/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[structural-patterns]]></category>
            <category><![CDATA[hoc]]></category>
            <category><![CDATA[hooks]]></category>
            <category><![CDATA[composition]]></category>
            <category><![CDATA[best-practices]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Structural patterns organize relationships between objects and classes. The Gang of Four documented Decorator, Adapter, Facade, Composite, and Proxy in 1994 for C++ and Smalltalk. Modern TypeScript and React absorbed all five into framework conventions, hooks, and type-safe wrappers.</p>
<p>The practical question in a React codebase is where a wrapper belongs. Composition stays the default inside code you own: hooks for cross-cutting behavior, plain components for hierarchy. Wrapping earns its place at the edges you do not control, such as third-party APIs, SDK setup, and access rules.</p>
<p><strong>Decorator Pattern: Three Meanings in TypeScript</strong></p>
<p>The term "decorator" means three different things in TypeScript ecosystems:</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Builder Pattern in TypeScript: Type-Safe Configuration Across Modern Applications]]></title>
            <description><![CDATA[How the Builder pattern uses TypeScript's type system for safe, discoverable APIs across serverless, data layers, and testing, with working examples.]]></description>
            <link>https://sph.sh/en/posts/builder-pattern-typescript/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/builder-pattern-typescript/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[serverless]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 05 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The Builder pattern in TypeScript serves a different purpose than in traditional object-oriented languages. Java and C# use builders mainly to tame long lists of optional parameters. TypeScript can go further: generics and conditional types let a builder enforce constraints at compile time, so a misconfiguration surfaces as a type error in the IDE instead of a deployment failure.</p>
<p>That narrows where the pattern earns its keep. Reach for a builder when a configuration object carries interdependent constraints, which in practice means infrastructure definitions, query builders, and test data factories. Stay with a plain object literal or an options parameter everywhere else.</p>
<p><strong>The Problem with Complex TypeScript Objects</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Middy and Zod: Type-Safe AWS Lambda Middleware Validation]]></title>
            <description><![CDATA[Build maintainable, type-safe Lambda middleware with Middy's builder pattern, Zod validation, feature flags, and secrets management for serverless apps.]]></description>
            <link>https://sph.sh/en/posts/middy-builder-pattern-zod-validation/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/middy-builder-pattern-zod-validation/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[middy]]></category>
            <category><![CDATA[middleware]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[zod]]></category>
            <category><![CDATA[feature-flags]]></category>
            <category><![CDATA[aws-secrets-manager]]></category>
            <category><![CDATA[builder-pattern]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 05 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Middleware in a Lambda codebase drifts. Once a project has more than a handful of functions, every handler wires its own Middy chain: error handlers land in different positions, some endpoints authenticate and some do not, and a schema that no longer matches the handler signature fails only when traffic hits it.</p>
<p>A thin builder in front of Middy fixes that. It pins the middleware order in one place, carries the context types forward so TypeScript knows what the handler receives, and uses Zod instead of JSON Schema for validation. The cost is a few hundred lines of glue and a slightly heavier cold start, which is a trade worth taking for any API beyond a couple of endpoints.</p>
<p><strong>The Problem with Standard Middleware Patterns</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[OpenTelemetry Fundamentals: A Beginner's Guide to Modern Observability]]></title>
            <description><![CDATA[A beginner's guide to OpenTelemetry covering traces, metrics, and logs with practical implementation examples, common pitfalls, and a terminology glossary.]]></description>
            <link>https://sph.sh/en/posts/opentelemetry-fundamentals-beginner-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/opentelemetry-fundamentals-beginner-guide/</guid>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[distributed-tracing]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 05 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In distributed systems, a single user request can traverse dozens of services, so logs alone rarely explain a failure or a latency spike. Without a shared telemetry standard, every service emits data in its own format, and engineers correlate traces, metrics, and logs by hand across incompatible backends. OpenTelemetry (OTel) is the open-source framework that removes that mismatch: one vendor-neutral way to generate, process, and export telemetry from any language or runtime.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[7 Lesser-Known TypeScript Features: satisfies, Branded Types, and More]]></title>
            <description><![CDATA[Seven lesser-known TypeScript features that improve production code: satisfies, noUncheckedIndexedAccess, branded types, discriminated unions, and more.]]></description>
            <link>https://sph.sh/en/posts/typescript-essential-underutilized-features/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/typescript-essential-underutilized-features/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[type-safety]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[code-quality]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Turning on strict in tsconfig.json feels like the end of the type-safety conversation. It is not. Indexed access still returns a type that hides undefined, a UserID still slots into a function that expects an OrderID, and a member added to a union still slips past every switch you already wrote.</p>
<p>The highest-value change is noUncheckedIndexedAccess, a compiler flag that strict does not turn on. Add it first, then reach for the type-level tools that cover the rest: satisfies, branded types, discriminated unions with exhaustiveness checking, type predicates, template literal types, and infer. All seven are compile-time constructs, so none of them cost anything at runtime.</p>
<p><strong>The Gaps Strict Mode Leaves Open</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Network Fundamentals Every Software Developer Should Know]]></title>
            <description><![CDATA[A practical glossary of essential networking concepts for developers - from protocols and DNS to debugging tools and security basics.]]></description>
            <link>https://sph.sh/en/posts/network-fundamentals-developers-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/network-fundamentals-developers-guide/</guid>
            <category><![CDATA[networking]]></category>
            <category><![CDATA[http]]></category>
            <category><![CDATA[tcp-ip]]></category>
            <category><![CDATA[dns]]></category>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[backend-development]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[security]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 30 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mysterious timeouts, slow APIs, and TLS errors in distributed systems are almost always network problems disguised as application problems. When developers lack a working model of TCP, DNS, and HTTP, diagnosis stalls at the wrong layer and fixes are guesswork.</p>
<p>The concepts below are the ones that surface most often in backend development and production troubleshooting: protocols, the connection lifecycle, debugging tools, and security basics. Each entry names what the concept is, when it matters, and which command answers the question.</p>
<p><strong>Where Network Knowledge Pays Off</strong></p>
<p>Perfect application code still fails when data movement between client and server is not understood. Developers struggle with:</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[CloudEvents SDK for TypeScript: Standardizing Events in Serverless Architectures]]></title>
            <description><![CDATA[A practical guide to the CloudEvents spec and TypeScript SDK: create, parse, and validate standardized events across AWS Lambda and EventBridge.]]></description>
            <link>https://sph.sh/en/posts/cloudevents-sdk-typescript-serverless/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/cloudevents-sdk-typescript-serverless/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[cloudevents]]></category>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 29 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In event-driven architectures, every event source tends to describe events differently: one Lambda expects { userId: string }, another expects { user_id: string }, and a third uses { sub: string }. The cost of this heterogeneity is integration code that grows with the number of sources, and observability tools that cannot correlate events across systems. A standardized event envelope removes both problems; the cost is requiring every producer to adopt the same schema.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Domain-Driven Design: Introduction and Fundamentals]]></title>
            <description><![CDATA[A comprehensive introduction to Domain-Driven Design: core concepts, building blocks, strategic patterns, and when and how to apply DDD in practice.]]></description>
            <link>https://sph.sh/en/posts/domain-driven-design-introduction/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/domain-driven-design-introduction/</guid>
            <category><![CDATA[domain-driven-design]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[backend-development]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 29 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Domain-Driven Design (DDD) aligns the structure of your code with the business domain it serves. In complex, long-lived systems that alignment pays off: when classes and methods carry the names domain experts already use, business rules stay in one place instead of leaking across controllers and services. The cost is modeling time spent with people who know the business.</p>
<p>That cost is the whole decision. Rules that change often, a system you will own for years, and domain experts willing to sit with you: those three conditions make the investment worth it. Without them, a plain layered architecture serves you better.</p>
<p><strong>What is Domain-Driven Design?</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Event Storming: A Practical Guide to Understanding Complex Domains]]></title>
            <description><![CDATA[A hands-on guide to Event Storming: what it is, how to facilitate sessions effectively, and when to use this workshop technique for domain modeling.]]></description>
            <link>https://sph.sh/en/posts/event-storming-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/event-storming-guide/</guid>
            <category><![CDATA[domain-driven-design]]></category>
            <category><![CDATA[event-storming]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[agile-practices]]></category>
            <category><![CDATA[team-collaboration]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 28 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A business process is hard to write down accurately. Interviews capture one person's slice of it, and the specification that follows misses the steps nobody thought to mention. Event Storming closes that gap in a workshop: business experts and engineers put domain events on a wall in chronological order until the whole process is visible at once.</p>
<p>If you have never run one, start with the Big Picture format and keep business experts in the room for all of it. Everything else the technique offers (detailed process design, aggregate boundaries, service boundaries) rests on the shared map that first session produces, and a session without domain experts turns into a design meeting where engineers guess.</p>
<p><strong>What is Event Storming</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dokploy + Cloudflare Tunnel on a VPS: Setup Guide]]></title>
            <description><![CDATA[A practical guide to setting up a secure, affordable private server using VPS, Dokploy for deployments, and Cloudflared tunnels for secure access without exposing ports]]></description>
            <link>https://sph.sh/en/posts/cost-effective-vps-dokploy-cloudflared-setup/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/cost-effective-vps-dokploy-cloudflared-setup/</guid>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[vps]]></category>
            <category><![CDATA[cloudflare]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[self-hosting]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[dokploy]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 26 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Running a private server doesn't have to mean expensive cloud bills or complex infrastructure. The sweet spot often lies in combining simple, focused tools rather than reaching for enterprise platforms.</p>
<p>A production-ready private server needs three pieces:
- <strong>VPS</strong> for affordable compute (~\$5-20/month)
- <strong>Dokploy</strong> for Docker-based deployments with a clean UI
- <strong>Cloudflared</strong> for secure access without opening ports</p>
<p>Together they cost about \$6-8 per month and handle multiple applications securely.</p>
<p><strong>Prerequisites</strong></p>
<p>Before starting, you'll need:
- A domain name (for Cloudflare tunnel)
- Basic terminal/SSH knowledge
- A Cloudflare account (free tier works)
- ~30 minutes of setup time</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building CRM Systems with Event-Driven Architecture]]></title>
            <description><![CDATA[A practical guide to implementing customer relationship management using event sourcing, CQRS, and event-driven patterns for marketing automation and consent management]]></description>
            <link>https://sph.sh/en/posts/crm-event-driven-architecture/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/crm-event-driven-architecture/</guid>
            <category><![CDATA[event-driven-architecture]]></category>
            <category><![CDATA[cqrs]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[gdpr]]></category>
            <category><![CDATA[marketing-automation]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[event-sourcing]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 26 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Traditional CRM systems store customer state directly: a record per customer, mutated in place with each interaction. That model breaks when the product needs real-time personalization, GDPR-compliant audit trails, and multi-channel orchestration at the same time. The current-state row has no memory of how it got there. Event-driven CRM inverts the model: every interaction is captured as an immutable event, and any view (profile, consent ledger, channel history) is a projection over that event stream.</p>
<p>A workable default: event sourcing on AWS with EventBridge and DynamoDB, plus CQRS projections for personalization and consent. The bill arrives as storage cost, eventual-consistency budgets, and replay windows.</p>
<p><strong>The Event-Driven CRM Landscape</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Strapi vs Contentful vs Storyblok vs Kontent.ai: A Headless CMS Comparison]]></title>
            <description><![CDATA[A practical comparison of headless CMS options (Strapi, Contentful, Kontent, Storyblok) with Cloudinary image management and framework integration.]]></description>
            <link>https://sph.sh/en/posts/multi-channel-content-management-headless-cms/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/multi-channel-content-management-headless-cms/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[cms]]></category>
            <category><![CDATA[api-design]]></category>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[content-management]]></category>
            <category><![CDATA[cloudinary]]></category>
            <category><![CDATA[mobile-development]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 26 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Choosing a headless CMS gets hard the moment one content set has to feed a website, a mobile app, and whatever surface comes next. Four platforms dominate that shortlist: Strapi, Contentful, Kontent.ai, and Storyblok. They differ less in what they can store than in who absorbs the cost of using them, so the decision comes down to one question: is the bottleneck your content team or your engineering team?</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Traefik vs Nginx: A Practical Introduction for Nginx Users]]></title>
            <description><![CDATA[A practical introduction to Traefik for developers familiar with nginx. Learn core concepts, setup examples, and when to choose Traefik over traditional reverse proxies.]]></description>
            <link>https://sph.sh/en/posts/traefik-101-introduction/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/traefik-101-introduction/</guid>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[traefik]]></category>
            <category><![CDATA[nginx]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[reverse-proxy]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 26 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Traefik is a reverse proxy and load balancer built for infrastructure that changes while it runs. It watches your container orchestrator and updates its own routing rules; nginx expects you to edit a config file and reload. That single difference decides most of the choice. If your services are containers that come and go on their own schedule, put Traefik at that edge and keep nginx where the topology is stable.</p>
<p>The trade is operational convenience against a leaner data path. Traefik removes the edit-test-reload loop, and pays for it with a larger runtime footprint and a less mature rewrite and caching engine. For a fixed set of upstreams serving static files, that is a bad trade. For a fleet of containers redeployed several times a day, it is an easy one.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The AI Assistance Spectrum: Choosing the Right Level for Professional Software Engineering]]></title>
            <description><![CDATA[A framework for six levels of AI assistance in software, from code review to vibe coding, with guidance on when to dial AI help up or down.]]></description>
            <link>https://sph.sh/en/posts/ai-assistance-spectrum-professional-software-engineering/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/ai-assistance-spectrum-professional-software-engineering/</guid>
            <category><![CDATA[ai-tools]]></category>
            <category><![CDATA[code-quality]]></category>
            <category><![CDATA[developer-productivity]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[github-copilot]]></category>
            <category><![CDATA[cursor]]></category>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[development-workflow]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 24 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>How much AI assistance belongs in a professional engineering workflow? The useful answer is not yes or no but a level. Above a fully manual baseline sit six of them: review-only lookups, inline autocomplete, function generation, multi-file refactoring, autonomous agents, and "vibe coding," where nobody reads the output. Each step up buys speed and spends comprehension.</p>
<p>For most production code the defensible default is function-level generation. You write the signature and the intent, the model writes the body, and you review it like any other diff. Dial that down in regulated or security-critical paths, and up only where test coverage is strong enough to catch what the model gets wrong.</p>
<p><strong>The Core Problem</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI Developer Tools Landscape: Adoption Data and Trends]]></title>
            <description><![CDATA[Where AI coding assistants actually help, why individual speed gains stall at the team level, and what to put in place before widening adoption.]]></description>
            <link>https://sph.sh/en/posts/ai-tools-developers-part-1/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/ai-tools-developers-part-1/</guid>
            <category><![CDATA[ai-tools]]></category>
            <category><![CDATA[developer-productivity]]></category>
            <category><![CDATA[github-copilot]]></category>
            <category><![CDATA[cursor]]></category>
            <category><![CDATA[enterprise-adoption]]></category>
            <category><![CDATA[dora-metrics]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AI coding assistants have reached near-universal adoption: DORA's 2025 research puts usage at around 90% of technologists. In the same period, a randomized trial by METR measured experienced open-source developers completing tasks 19% slower when they were allowed to use AI tools. Those same developers believed the tools had sped them up by 20%. Both findings can hold at once, and together they explain why so many rollouts stall.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Adopt AI Coding Tools: From Pilot to Production]]></title>
            <description><![CDATA[A hands-on guide to adopting AI developer tools: readiness scoring, pilot scope, security controls, review capacity, and the metrics worth tracking.]]></description>
            <link>https://sph.sh/en/posts/ai-tools-developers-part-2/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/ai-tools-developers-part-2/</guid>
            <category><![CDATA[ai-tools]]></category>
            <category><![CDATA[implementation]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[sonarqube]]></category>
            <category><![CDATA[production-patterns]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Rolling AI coding tools out to an existing engineering organization fails on second-order costs rather than on the tools. Review queues lengthen, security controls written for human-authored code stop covering the surface, and the productivity story is not the one the pilot deck promised: METR's randomized study found experienced developers took 19% longer on their own repositories when they were allowed to use AI assistance, even though they expected to be faster. METR has since widened that estimate and reported a follow-up pointing the other way. Treat the size of the productivity effect as unsettled; the second-order costs are the part you can plan for.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI Coding Tools Security Risks and Governance]]></title>
            <description><![CDATA[Security risks, governance, and trust building for AI developer tools: the 2025 CVEs, shadow AI discovery, and an incident response playbook for leaked secrets.]]></description>
            <link>https://sph.sh/en/posts/ai-tools-developers-part-3/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/ai-tools-developers-part-3/</guid>
            <category><![CDATA[ai-security]]></category>
            <category><![CDATA[governance]]></category>
            <category><![CDATA[trust]]></category>
            <category><![CDATA[shadow-ai]]></category>
            <category><![CDATA[incident-response]]></category>
            <category><![CDATA[cve-2025]]></category>
            <category><![CDATA[compliance]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AI coding assistants introduce a distinct class of security risk: they generate plausible-looking secrets from training data and normalize patterns that slip past standard secret-scanning heuristics. The default that survives contact with this is narrow. Treat every AI suggestion as untrusted input, scan it before it reaches a commit, and give the tool the least repository access it can work with. Everything else in an AI governance program is a way of enforcing that default at organizational scale.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI Coding Tools ROI: Measuring Real Business Value]]></title>
            <description><![CDATA[A year-one ROI model for AI developer tools: the cost categories vendors leave out, a go/no-go framework, and the conditions that should change the decision.]]></description>
            <link>https://sph.sh/en/posts/ai-tools-developers-part-4/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/ai-tools-developers-part-4/</guid>
            <category><![CDATA[ai-roi]]></category>
            <category><![CDATA[cost-analysis]]></category>
            <category><![CDATA[future-planning]]></category>
            <category><![CDATA[strategic-decisions]]></category>
            <category><![CDATA[ai-adoption]]></category>
            <category><![CDATA[business-value]]></category>
            <category><![CDATA[roadmap]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AI developer tooling budgets fail in a predictable direction. The seat price looks like the decision, and since GitHub moved Copilot to usage-based billing in June 2026 it is not even a fixed number. The lines that dominate year-one spend are the ones no vendor model contains: metered usage stacked on top of the seat, the productivity dip while everyone learns the tools, reviewer capacity to absorb a larger pull-request queue, and security controls retrofitted after the first incident.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lambda Layer Versioning Strategies for Multi-Environment Deployments]]></title>
            <description><![CDATA[Practical approaches to managing Lambda Layer versions across dev, staging, and production with AWS CDK, automated deployment pipelines, and rollbacks.]]></description>
            <link>https://sph.sh/en/posts/lambda-layer-versioning-multi-environment/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/lambda-layer-versioning-multi-environment/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[deployment]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>AWS Lambda gives a layer an auto-incrementing integer and nothing above it. There is no semantic version, no promotion path between environments, and no record of which build a given function is running. Teams close that gap with whatever is nearest to hand, which is how dev, staging, and production end up on three different layer versions that nobody can map back to a change.</p>
<p>The default worth starting from is a version manifest: a YAML file in the repository that pins an explicit layer ARN per environment, promoted by commit and rolled back by revert. It gives you a Git audit trail and no lookups at deploy time. Three other strategies handle the cases where a hand-edited file does not fit.</p>
<p><strong>When Layer Versions Diverge</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Role Expectations in Software Teams: RACI and DACI Frameworks]]></title>
            <description><![CDATA[Unclear ownership stalls software delivery. How RACI and DACI assign decision rights, where each one fits, and the pitfalls that kill adoption.]]></description>
            <link>https://sph.sh/en/posts/role-expectations-team-performance/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/role-expectations-team-performance/</guid>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[engineering-management]]></category>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[remote-work]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Role ambiguity in software teams shows up as stalled work, not as an argument about roles. Gallup's 2024 US workplace research found that 46% of employees cannot say what is expected of them at work. When engineers don't know who owns a decision, the work either waits for a meeting or gets built twice on separate branches.</p>
<p>Two lightweight frameworks cover most of it. Use RACI for recurring workflows such as API design, deployment, and requirements, and DACI for one-off decisions such as architecture and tool selection. The default worth starting from is narrow: pick the two or three workflows that generate the most rework, write down who decides, and leave everything else informal until it starts costing you.</p>
<p><strong>The Cost of Role Ambiguity</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Next.js Deployment Alternatives to Vercel: A Comprehensive Guide]]></title>
            <description><![CDATA[A practical guide to deploying Next.js beyond Vercel, covering the cost profile, implementation details, and migration path for each target platform.]]></description>
            <link>https://sph.sh/en/posts/vercel-alternatives-nextjs-deployment/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/vercel-alternatives-nextjs-deployment/</guid>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[deployment]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[cloudflare]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[infrastructure]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Tue, 30 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Vercel gives Next.js the smoothest deploy experience available, but the bill scales faster than the traffic. Bandwidth overages run \$0.15/GB and function invocations accumulate quietly, so a single marketing spike can multiply costs at the exact moment conversions matter most. The platform-specific APIs that make deployment easy also create lock-in that complicates any future move.</p>
<p>Cloudflare Workers with the OpenNext adapter is the default worth trying first for most Next.js applications: bandwidth is not metered, and the adapter keeps the application portable if the answer changes later. SST on AWS and a Docker VPS behind a CDN cover the cases where that default does not fit. Each target carries its own cost profile and its own migration details that the docs leave out.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GitHub Spec Kit: A Guide to Spec-Driven AI Development]]></title>
            <description><![CDATA[How GitHub's Spec Kit turns loose AI code generation into structured, maintainable output through a four-phase specify-plan-tasks-implement loop.]]></description>
            <link>https://sph.sh/en/posts/github-speckit-ai-specification-framework/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/github-speckit-ai-specification-framework/</guid>
            <category><![CDATA[github]]></category>
            <category><![CDATA[ai-tools]]></category>
            <category><![CDATA[code-quality]]></category>
            <category><![CDATA[development-tools]]></category>
            <category><![CDATA[specification-driven]]></category>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[copilot]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 24 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Code generated by AI tools often passes a local smoke test but fails the production bar: ambiguous interfaces, unverified assumptions about upstream data, missing error handling, and structure that reflects the prompt rather than the codebase's conventions. The gap between "works in isolation" and "ships to production" is not a model capability problem; it is a specification problem. A clear, machine-readable specification of the interface, inputs, invariants, and error modes closes most of that gap before the model generates a single line.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Ephemeral Preview Environments with AWS CDK and Serverless]]></title>
            <description><![CDATA[Learn to build automated preview environments using AWS CDK, Lambda, and GitHub Actions for seamless PR testing and review workflows]]></description>
            <link>https://sph.sh/en/posts/ephemeral-preview-environments-aws-serverless-cdk/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/ephemeral-preview-environments-aws-serverless-cdk/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[devops-monitoring]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sun, 21 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>The Problem with Shared Staging Environments</strong></p>
<p>A single shared staging environment becomes a bottleneck once a team opens more than one pull request per day against it. Concurrent PRs overwrite each other's database state, flap on the same feature flags, and compete for the same DNS record; test signal drops because a failure is as likely to mean "someone else's PR" as a real regression.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What Is a Key-Value Store? Choosing the Right Solution]]></title>
            <description><![CDATA[A foundational guide to key-value storage: what it is, where it fits, why teams choose it, and which solutions ship with which technology stacks.]]></description>
            <link>https://sph.sh/en/posts/redis-key-value-storage-fundamentals/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/redis-key-value-storage-fundamentals/</guid>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[key-value-storage]]></category>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 15 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Applying relational database patterns to key-value access workloads (session storage, caching, cart data) causes avoidable latency and schema complexity. Choosing the wrong storage model forces teams into index-tuning cycles that cannot fix a fundamental architectural mismatch. Key-value storage removes the mismatch: the data model matches the access pattern, so a lookup costs one hash probe instead of a query plan.</p>
<p>For distributed workloads, Redis is the default worth beating. The exceptions are narrow and predictable: etcd for configuration and coordination, DynamoDB for serverless traffic that swings, an in-process cache when there is only one server, and embedded Hazelcast when the whole deployment runs on the JVM. The work is recognizing which case you are in.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Claude Code MCP Servers: Setup and Configuration Guide]]></title>
            <description><![CDATA[A comprehensive guide to Claude Code, AI agents, and Model Context Protocol servers that transforms developers from basic users to power users]]></description>
            <link>https://sph.sh/en/posts/claude-code-mcp-ecosystem-mastery/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/claude-code-mcp-ecosystem-mastery/</guid>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[mcp-servers]]></category>
            <category><![CDATA[ai-development]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 13 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Claude Code's productivity ceiling rises sharply once MCP (Model Context Protocol) servers join the picture. Without them the workflow stays in copy-paste mode; with the right MCP servers wired in, Claude Code can act directly against your infrastructure, databases, and services. The setup worth starting from is small: the filesystem server plus one documentation server, with everything else added task by task and switched off again afterwards.</p>
<p><strong>What Claude Code Is</strong></p>
<p>Claude Code is a development environment with capabilities that most developers never explore. Three of them matter more than the rest.</p>
<p><strong>The Three Interaction Modes</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Hidden Cost of Cultural Blindness: When Global Engineering Teams Fail]]></title>
            <description><![CDATA[Cultural misunderstandings quietly derail global engineering teams; practical frameworks for adapting feedback, meetings and escalation to cultural context.]]></description>
            <link>https://sph.sh/en/posts/cultural-misunderstandings-global-engineering-teams/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/cultural-misunderstandings-global-engineering-teams/</guid>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[global-teams]]></category>
            <category><![CDATA[cultural-intelligence]]></category>
            <category><![CDATA[remote-work]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 13 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Cultural blindness on global engineering teams is expensive in a quiet way: teams report "ready" while meaning different things, and the gap surfaces at integration, not in code. Communication style, hierarchy expectations, and feedback delivery differ sharply across cultures, and a distributed team that never names those differences pays for them in rework, missed escalations, and people who stop speaking up.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[RACI and DACI for Software Teams: Fixing Role Ambiguity]]></title>
            <description><![CDATA[Unclear role expectations quietly drain software team productivity; here are proven RACI, swim-lane, and escalation frameworks to remove the waste and lift performance.]]></description>
            <link>https://sph.sh/en/posts/hidden-cost-role-ambiguity-software-team-performance/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/hidden-cost-role-ambiguity-software-team-performance/</guid>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[role-clarity]]></category>
            <category><![CDATA[team-performance]]></category>
            <category><![CDATA[raci-matrix]]></category>
            <category><![CDATA[remote-teams]]></category>
            <category><![CDATA[engineering-management]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 13 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Role ambiguity (unclear ownership of decisions and deliverables) quietly drains engineering throughput through duplicated work, decision paralysis, and escalation overhead that never appears on any sprint board. Left unaddressed, it compounds across distributed teams where informal coordination no longer fills the gaps. Three frameworks remove most of that waste without adding process overhead: RACI matrices, swim-lane definitions, and escalation protocols.</p>
<p>If you adopt only one, make it RACI and apply it to recurring work such as API design, deployments, and requirement sign-off. Keep DACI for one-off architectural calls. The rule underneath both is the same: exactly one person is accountable for a decision, and everyone else knows whether they are consulted or only informed.</p...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Working with Difficult Coworkers in Software Teams: Beyond Textbook Solutions]]></title>
            <description><![CDATA[A field guide to engineering-specific difficult coworkers, from code-review blockers to ghost colleagues, with practical strategies that work for each archetype.]]></description>
            <link>https://sph.sh/en/posts/managing-difficult-coworkers-software-engineering-teams/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/managing-difficult-coworkers-software-engineering-teams/</guid>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[workplace-psychology]]></category>
            <category><![CDATA[team-dynamics]]></category>
            <category><![CDATA[remote-work]]></category>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[agile]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 13 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Generic workplace advice about difficult coworkers fails on engineering teams because the friction has technical roots. A blocked deployment, a code review that turns into a 47-message naming debate, or a teammate who vanishes during an outage are not personality clashes you can resolve with the standard Pessimist, Passive-Aggressive, and Know-It-All playbook. The behavior is amplified by code ownership, review authority, and on-call pressure.</p>
<p>That mismatch produces archetypes the management literature does not cover: the perfectionist blocker, the technical purist, the ghost colleague. Each one needs a different response, and that response is usually a process change; direct confrontation is rarely the first move.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Security Glossary: 50+ Terms Every Dev Team Should Know]]></title>
            <description><![CDATA[Definitions, implementation context, and defaults for authentication, token, access control, and Zero Trust terminology that teams argue about.]]></description>
            <link>https://sph.sh/en/posts/security-glossary-appendix/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/security-glossary-appendix/</guid>
            <category><![CDATA[security]]></category>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[oauth2]]></category>
            <category><![CDATA[mfa]]></category>
            <category><![CDATA[biometric]]></category>
            <category><![CDATA[zero-trust]]></category>
            <category><![CDATA[jwt]]></category>
            <category><![CDATA[saml]]></category>
            <category><![CDATA[oidc]]></category>
            <category><![CDATA[rbac]]></category>
            <category><![CDATA[abac]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 13 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Security terminology confusion turns into architecture mistakes. A team that builds login on OAuth2 alone finds out weeks later that OAuth2 grants authorization and says nothing about identity. The fix is OIDC, and by then the token handling is already wired the wrong way on both the client and the API.</p>
<p>The terms below cover authentication, tokens, access control, and the Zero Trust vocabulary that shows up in design reviews. Each entry gives the definition, the implementation detail that decides whether it holds up, and the misconception that usually costs time. Where a sane default exists (OIDC over SAML for new systems, passkeys over passwords, short-lived access tokens over convenient ones), it is named.</p>
<p><strong>Authentication Fundamentals</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Team Conflict Resolution: A Field Guide to Turning Dysfunction into High Performance]]></title>
            <description><![CDATA[A field guide to spotting, managing, and resolving conflict in software teams, with practical frameworks and early-warning systems that turn friction into performance.]]></description>
            <link>https://sph.sh/en/posts/team-conflict-resolution-field-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/team-conflict-resolution-field-guide/</guid>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[remote-work]]></category>
            <category><![CDATA[conflict-resolution]]></category>
            <category><![CDATA[engineering-culture]]></category>
            <category><![CDATA[psychological-safety]]></category>
            <category><![CDATA[team-dynamics]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Sat, 13 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Engineering teams lose velocity not when conflict appears, but when they lack a framework to distinguish productive disagreement from toxic dysfunction. Unresolved architectural standoffs, escalating code-review threads, and rising attrition share the same root: no shared process for surfacing and resolving friction early. The default worth adopting is narrow: classify the conflict by type before choosing an intervention, and anchor that classification in a working agreement the team drafts itself.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[pnpm Catalogs: Fix Dependency Drift in Monorepos]]></title>
            <description><![CDATA[How pnpm catalogs stop dependency drift in JavaScript monorepos: one place to declare shared versions, named catalogs for legacy packages, and CI enforcement.]]></description>
            <link>https://sph.sh/en/posts/pnpm-catalog-dependency-drift-solution/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/pnpm-catalog-dependency-drift-solution/</guid>
            <category><![CDATA[pnpm]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[dependency-management]]></category>
            <category><![CDATA[monorepo]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Wed, 10 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In a JavaScript monorepo, dependency drift starts quietly. One service declares lodash at ^4.17.20, another at ^4.17.21, and the installer gives you both. Weeks later a shared helper behaves differently depending on which service imports it, and the bug reproduces on one machine out of five.</p>
<p>pnpm catalogs close that gap at the source. Each shared version is declared once in pnpm-workspace.yaml, packages reference it with catalog:, and a CI check fails the build when a package declares a version inline. The trade is that one file becomes a shared bottleneck: exactly what you want for react, and exactly what you do not want for a parser only one service uses.</p>
<p><strong>The Dependency Drift Challenge</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Write a Technical RFC: Section-by-Section Guide]]></title>
            <description><![CDATA[A section-by-section guide to technical RFCs: what each part has to establish, what reviewers look for, and where proposals stall in review.]]></description>
            <link>https://sph.sh/en/posts/anatomy-of-technical-rfc/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/anatomy-of-technical-rfc/</guid>
            <category><![CDATA[rfc]]></category>
            <category><![CDATA[technical-writing]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[documentation]]></category>
            <category><![CDATA[engineering-process]]></category>
            <category><![CDATA[stakeholder-management]]></category>
            <category><![CDATA[communication]]></category>
            <category><![CDATA[decision-making]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>An RFC for a critical system stalls for one of two reasons: reviewers cannot tell what problem it solves, or they cannot find the part that concerns them. Both are structural problems, and both are fixable before the first review comment lands.</p>
<p>Treat the RFC as a sales document. It sells one solution to four audiences with competing priorities: executives who fund the work, architects who vet the design, implementers who build it, and operators who carry the pager afterwards. Structure it so each audience finds its answer without reading the whole document, and put those answers in the order the audiences ask for them.</p>
<p><strong>The Audience Sets the Order</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Authentication Strategies by Industry: Banking, Healthcare, E-commerce, SaaS]]></title>
            <description><![CDATA[One-size-fits-all auth is a myth: banking, healthcare, e-commerce and SaaS each shape the authentication architecture differently.]]></description>
            <link>https://sph.sh/en/posts/authentication-authorization-strategies-by-business-domain/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/authentication-authorization-strategies-by-business-domain/</guid>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[oauth2]]></category>
            <category><![CDATA[jwt]]></category>
            <category><![CDATA[compliance]]></category>
            <category><![CDATA[fintech]]></category>
            <category><![CDATA[healthcare]]></category>
            <category><![CDATA[enterprise]]></category>
            <category><![CDATA[saml]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Authentication and authorization are not a single technical decision; they are a composition of regulatory constraints, user expectations, failure modes, and audit requirements that differ sharply by business domain. A banking authentication flow and a social media one answer the same mechanical question ("is this request from the claimed user?") but the acceptable answers diverge on session length, multi-factor strength, recovery paths, and what a lockout looks like. Reusing an auth stack across domains rarely fails on the crypto primitives. It fails on the edge cases those domains handle differently: step-up auth in banking, delegated identity in IoT, graceful degradation in social.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Code Review Culture: From Nitpicking to Knowledge Sharing]]></title>
            <description><![CDATA[How to transform code reviews from fault-finding into mentorship and learning opportunities that build psychological safety while improving code quality.]]></description>
            <link>https://sph.sh/en/posts/code-review-culture-nitpicking-to-knowledge-sharing/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/code-review-culture-nitpicking-to-knowledge-sharing/</guid>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[team-culture]]></category>
            <category><![CDATA[mentorship]]></category>
            <category><![CDATA[psychological-safety]]></category>
            <category><![CDATA[knowledge-sharing]]></category>
            <category><![CDATA[ai-tools]]></category>
            <category><![CDATA[remote-work]]></category>
            <category><![CDATA[onboarding]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A recurring signal in team retrospectives: junior developers who prefer working alone on a feature for two weeks over submitting code for review. That preference points to a specific failure mode in review culture.</p>
<p>The instinct behind rigorous reviews is sound, but the execution often creates an environment where getting code reviewed feels like defending a dissertation to a committee of critics more interested in finding faults than helping someone succeed.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GitHub Copilot ROI: An Enterprise Cost Analysis Framework]]></title>
            <description><![CDATA[How to model GitHub Copilot ROI at enterprise scale: the license and review cost lines, the metrics that matter, payback shape by team size, and rollout anti-patterns.]]></description>
            <link>https://sph.sh/en/posts/copilot-to-production-cost-analysis/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/copilot-to-production-cost-analysis/</guid>
            <category><![CDATA[github-copilot]]></category>
            <category><![CDATA[ai-tools]]></category>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[roi-analysis]]></category>
            <category><![CDATA[code-quality]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[enterprise]]></category>
            <category><![CDATA[cost-analysis]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <category><![CDATA[migration]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Measuring the return on an AI coding assistant requires separating productivity-proxy metrics (keystroke velocity, completion acceptance rate) from the outcomes the tool is supposed to move (delivery cycle time, defect rate, maintenance cost). Vendor-reported numbers are usually the first set; the second set determines whether the investment pays back. A controlled experiment run by GitHub researchers measured a 55.8% faster completion time on a scoped task. DORA's 2025 report, surveying delivery outcomes across thousands of organizations, finds AI adoption positively related to delivery throughput and still negatively related to delivery stability. Both findings can hold at once, and the gap between them is where the budget goes.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Database Query Profiling: Systematic Optimization Journey]]></title>
            <description><![CDATA[Systematic profiling for PostgreSQL and MongoDB: how to find the queries driving latency and infrastructure cost, and which fixes actually move them.]]></description>
            <link>https://sph.sh/en/posts/database-query-profiling-100k-optimization/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/database-query-profiling-100k-optimization/</guid>
            <category><![CDATA[database-optimization]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[mongodb]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[profiling]]></category>
            <category><![CDATA[indexing]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <category><![CDATA[infrastructure]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Slow database queries are the most common source of runaway infrastructure costs in SaaS products: product searches taking 8 seconds and dashboards stalling for 45 seconds are symptoms, not root causes. Adding servers suppresses the symptoms while compounding the bill. Profile first, and scale only the part that profiling cannot explain. On PostgreSQL that means pg<em>stat</em>statements plus EXPLAIN; on MongoDB it means the database profiler plus an explain plan on the pipeline.</p>
<p><strong>Symptoms in a Multi-Tenant SaaS</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lewis Deep Democracy in Engineering Teams: Beyond False Consensus]]></title>
            <description><![CDATA[How Arnold Mindell's Deep Democracy principles transform technical decision-making, build psychological safety, and ensure every voice strengthens architecture.]]></description>
            <link>https://sph.sh/en/posts/deep-democracy-engineering-teams/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/deep-democracy-engineering-teams/</guid>
            <category><![CDATA[deep-democracy]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[team-dynamics]]></category>
            <category><![CDATA[decision-making]]></category>
            <category><![CDATA[psychological-safety]]></category>
            <category><![CDATA[inclusive-culture]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[leadership]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Technical decisions that look unanimous often carry no real agreement behind them. The cause is usually rank and thin psychological safety rather than weak engineering. Arnold Mindell's Deep Democracy gives engineering groups a workable default: treat dissent as design input, record it next to the decision, and set review triggers that fire when the minority concern turns out to be right.</p>
<p><strong>The Hidden Costs of False Consensus</strong></p>
<p>In many architecture reviews everyone visibly agrees, then the decision quietly reverses six months later because no one felt safe voicing concerns. The pattern repeats wherever teams mistake silence for agreement.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI Code Review vs Human Review: What Each Catches]]></title>
            <description><![CDATA[Where AI-assisted code review catches what humans miss, where humans still excel, and how to build effective human-AI collaboration in your review process.]]></description>
            <link>https://sph.sh/en/posts/llm-code-review-ai-finds-what-humans-miss/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/llm-code-review-ai-finds-what-humans-miss/</guid>
            <category><![CDATA[ai-code-review]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[code-quality]]></category>
            <category><![CDATA[mentorship]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[prompts]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Human code review misses a predictable class of defects: subtle SQL injection in a query builder that reads fine in isolation, the same flawed pattern repeated across every service that copied it, the security check skipped at the end of a long week. Reviewers focus on the diff in front of them, so systemic and cross-codebase issues slip through even when a senior engineer signs off.</p>
<p>AI reviewers catch exactly those patterns, but they miss business logic and architectural fit. The useful framing is not whether AI replaces human review; it is how to pair AI pattern recognition with human judgment so each covers the other's blind spot. Run both in parallel and consolidate the findings. Here is what each side catches and how to combine them.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating from Microservices Back to a Modular Monolith]]></title>
            <description><![CDATA[Recognizing distributed monoliths, strategic service consolidation, and the honest reality of moving back to a modular monolith when complexity grows.]]></description>
            <link>https://sph.sh/en/posts/monolith-revenge-microservices-technical-debt/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/monolith-revenge-microservices-technical-debt/</guid>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[monolith]]></category>
            <category><![CDATA[modular-monolith]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[service-consolidation]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[technical-debt]]></category>
            <category><![CDATA[team-productivity]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>When a microservices architecture evolves into a distributed monolith, teams face the exact problems they were trying to avoid. Deployment coupling, data consistency struggles, and coordination overhead are the visible symptoms.</p>
<p>Consolidating those services into a modular monolith is a valid architectural pattern, not an admission of failure. For a team of roughly 50 engineers or fewer, the sensible default is one well-structured deployable per product team. Extract a service out of it only when a measured need justifies the operational cost.</p>
<p><strong>When Adding a Cart Item Touches 47 Services</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating from Node.js to Go on AWS Lambda: A Practical Guide]]></title>
            <description><![CDATA[When a Node.js to Go move on AWS Lambda pays for itself and when it does not: the decision framework, the serverless Go patterns, and the cost math behind the call.]]></description>
            <link>https://sph.sh/en/posts/nodejs-to-go-serverless-migration-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/nodejs-to-go-serverless-migration-guide/</guid>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[migration]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Serverless bills have a way of growing faster than expected, and language choice is one of the first levers teams reach for. Node.js to Go is the usual candidate: a compiled binary with a small resident footprint, running on the same Lambda platform and the same event payloads.</p>
<p>The useful default is narrow. Stay on Node.js unless one specific service has a measurable volume, latency, or memory-cost problem, and then migrate that service alone. Go earns its keep on high-volume handlers with simple logic. It rarely earns anything on the complex business-logic service that took two years to get right, and knowing when not to migrate is the harder half of the decision.</p>
<p><strong>When Go Makes Sense on Lambda</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Moment.js Alternatives for Node.js: Migration Guide and Comparison]]></title>
            <description><![CDATA[Why time bugs hide in production, how to migrate from Moment.js to Day.js or date-fns, and how to keep UTC everywhere with conversion only at the display boundary.]]></description>
            <link>https://sph.sh/en/posts/nodejs-zaman-yonetimi-zamana-hukmetmek/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/nodejs-zaman-yonetimi-zamana-hukmetmek/</guid>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[time-management]]></category>
            <category><![CDATA[moment-js]]></category>
            <category><![CDATA[dayjs]]></category>
            <category><![CDATA[date-fns]]></category>
            <category><![CDATA[timezone]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <category><![CDATA[migration]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Time handling in production systems is a source of silent bugs because the defaults (local system time, implicit timezone, new Date() parsing) differ across nodes, languages, and layers. A payment rejected for a "past date transaction" even though the request carries a current local date is usually a timezone-offset bug between the client's wall clock, the application server's interpretation, and the database's storage format. UTC-everywhere with explicit offset conversion at the display boundary removes this entire class of bug, but requires discipline at every API, log, schema, and test fixture boundary.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Observability Beyond Metrics: The Art of System Storytelling]]></title>
            <description><![CDATA[Move past green-light dashboards to observability that narrates system behavior, user journeys, and business impact via distributed tracing.]]></description>
            <link>https://sph.sh/en/posts/observability-beyond-metrics-system-storytelling/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/observability-beyond-metrics-system-storytelling/</guid>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[distributed-tracing]]></category>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[grafana]]></category>
            <category><![CDATA[incident-response]]></category>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[storytelling]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Every dashboard is green, every metric sits inside its threshold, and customers still report broken checkouts. Aggregate metrics describe components one at a time, so a failure that lives in the path between components stays invisible to each of them.</p>
<p>Distributed tracing closes that gap. A single trace follows one request through every service it touches and records the order in which things happened. The default worth reaching for is one complete user journey instrumented end to end, with business context attached to every span.</p>
<p><strong>What a Single Trace Shows</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deep Democracy Between Product and Tech Teams: From Deadline Dictatorship to Collaborative Delivery]]></title>
            <description><![CDATA[Turn adversarial product-engineering standoffs into collaborative delivery with Deep Democracy principles that surface dissent and reduce burnout.]]></description>
            <link>https://sph.sh/en/posts/product-tech-deep-democracy/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/product-tech-deep-democracy/</guid>
            <category><![CDATA[product-management]]></category>
            <category><![CDATA[engineering-management]]></category>
            <category><![CDATA[collaboration]]></category>
            <category><![CDATA[team-dynamics]]></category>
            <category><![CDATA[agile]]></category>
            <category><![CDATA[communication]]></category>
            <category><![CDATA[burnout]]></category>
            <category><![CDATA[remote-work]]></category>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[organizational-culture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Product and Engineering often pull in opposite directions on the same scope. Product wants three months of features in six weeks; Engineering's estimates show twelve weeks; the meeting ends in a "compromise" that satisfies neither and ships buggy code into a quarter spent fixing fires. The cycle isn't bad luck or weak leadership; it's a missing decision-making system.</p>
<p>The teams that escape the cycle run a different decision process. The default worth adopting comes from Arnold Mindell's "Deep Democracy": surface every voice, including the dissenting one, before the group commits. Applied to product and engineering, it turns sprint planning from a negotiation into a design session with explicit rank, explicit assumptions and explicit trade-offs.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[From RFC to Production: What They Don't Tell You About Implementation]]></title>
            <description><![CDATA[Where RFC designs diverge from production reality, using notification systems as the worked example, and how to tell useful adaptation from architectural drift.]]></description>
            <link>https://sph.sh/en/posts/rfc-to-production-implementation-reality/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/rfc-to-production-implementation-reality/</guid>
            <category><![CDATA[rfc]]></category>
            <category><![CDATA[implementation]]></category>
            <category><![CDATA[production]]></category>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[project-management]]></category>
            <category><![CDATA[technical-debt]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>RFCs rarely survive contact with production unchanged, and that is not automatically a failure. Architecture diagrams that looked clean in review become tangled six months later, as timeline pressure, missing requirements, and operational realities force trade-offs that were never in the design.</p>
<p>The pattern is not bad design or bad engineers. It is the normal cost of building complex systems under business constraints. The useful skill is separating signal from drift: a gap is signal when the design met a requirement nobody could have known about at review time, and drift when the design was abandoned because following it was inconvenient. Notification systems make a good worked example, because the specification is short and the operational surface is unusually wide.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a Scalable User Notification System: Architecture and Database Design]]></title>
            <description><![CDATA[Design patterns, database schemas, and architectural decisions for building enterprise notification systems that handle millions of users]]></description>
            <link>https://sph.sh/en/posts/user-notification-system-part-1-architecture/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/user-notification-system-part-1-architecture/</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[notifications]]></category>
            <category><![CDATA[database-design]]></category>
            <category><![CDATA[scalability]]></category>
            <category><![CDATA[enterprise]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Multi-channel notification systems fail when teams model them as template-plus-send pipelines. A notification is a per-user routing decision: it may fan out, coalesce, suppress, or defer across email, SMS, push, and in-app channels. Without an explicit router layer, delivery guarantees collapse, user preferences get ignored, and compliance auditability becomes impossible.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real-time Notifications and Multi-Channel Delivery: WebSockets, Push, Email, and Beyond]]></title>
            <description><![CDATA[Implementation strategies for real-time notification delivery across WebSocket, push notification, email, SMS, and webhook channels with production-tested patterns]]></description>
            <link>https://sph.sh/en/posts/user-notification-system-part-2-realtime-delivery/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/user-notification-system-part-2-realtime-delivery/</guid>
            <category><![CDATA[websockets]]></category>
            <category><![CDATA[push-notifications]]></category>
            <category><![CDATA[email]]></category>
            <category><![CDATA[sms]]></category>
            <category><![CDATA[real-time]]></category>
            <category><![CDATA[channels]]></category>
            <category><![CDATA[delivery]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Real-time notifications sound simple until you deal with platform-specific push notification differences, WebSocket connection management at scale, and vendor costs that multiply with traffic.</p>
<p>Multi-channel notification systems reveal that the challenge isn't sending notifications; it is doing so reliably and at scale. Connection drain on WebSockets, token invalidation on push, bounce handling on email: every delivery mechanism carries its own quirks, limits, and failure modes.</p>
<p>Here are patterns for WebSocket connections, push notifications, email delivery, SMS, and webhooks that work in production environments. Each channel also carries its own latency expectation: WebSockets need sub-second delivery, while a few minutes is acceptable for email.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Debugging Notification Delivery Failures at Scale]]></title>
            <description><![CDATA[Real-world debugging techniques, monitoring strategies, and lessons learned from notification system failures in high-stakes production environments]]></description>
            <link>https://sph.sh/en/posts/user-notification-system-part-3-production-debugging/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/user-notification-system-part-3-production-debugging/</guid>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[production]]></category>
            <category><![CDATA[notifications]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[incident-response]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Notification systems fail in ways that hurt most: silently, at peak load, when a launch or a campaign depends on them. Welcome emails don't arrive, push notifications time out, and in-app alerts lag, but the dashboards stay green because each subsystem looks fine in isolation.</p>
<p>Two defaults hold up under this kind of failure: rank monitoring signals by their distance from the user, and put a circuit breaker in front of every dependency you do not control. CPU and memory alerts describe the wreckage accurately and say nothing about which subsystem started it.</p>
<p><strong>Cascade Failures Under Peak Traffic</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Notification Analytics and Performance Optimization: A/B Testing, Metrics, and Tuning at Scale]]></title>
            <description><![CDATA[Advanced analytics strategies, A/B testing frameworks, and performance optimization techniques for notification systems serving millions of users]]></description>
            <link>https://sph.sh/en/posts/user-notification-system-part-4-analytics-optimization/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/user-notification-system-part-4-analytics-optimization/</guid>
            <category><![CDATA[analytics]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[ab-testing]]></category>
            <category><![CDATA[metrics]]></category>
            <category><![CDATA[notifications]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[scalability]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Notification systems turn from basic delivery mechanisms into growth infrastructure through comprehensive analytics, systematic A/B testing, and performance optimization. Four building blocks carry that shift: multi-layered analytics pipelines, user journey tracking, safety-first experimentation, and cost-aware processing.</p>
<p>Once notification systems achieve basic functionality and stability, organizations face a new challenge: moving beyond simple delivery metrics to drive business growth. Product teams need answers about engagement rates, optimal timing, and content effectiveness. Engineering teams encounter performance bottlenecks as volume scales. Traditional monitoring approaches become insufficient when systems need to support millions of users while maintaining cost efficien...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Write an Effective RFC: A Guide for Engineers]]></title>
            <description><![CDATA[Practical guidance on RFC structure, stakeholder review, and turning technical debates into decisions a team actually keeps.]]></description>
            <link>https://sph.sh/en/posts/writing-effective-rfcs-principal-engineer-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/writing-effective-rfcs-principal-engineer-guide/</guid>
            <category><![CDATA[rfc]]></category>
            <category><![CDATA[technical-writing]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[decision-making]]></category>
            <category><![CDATA[documentation]]></category>
            <category><![CDATA[engineering-management]]></category>
            <category><![CDATA[stakeholder-management]]></category>
            <category><![CDATA[process]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Architectural disagreements surface late, when a feature is half-built and the cost of changing course is highest. Slack threads on the same decision drag across weeks, good ideas die in committee, and teams ship systems everyone "agreed" on yet interpreted differently. The RFC process exists to move that argument forward, before implementation locks the answer in.</p>
<p>An effective RFC is not documentation; it is the artifact that forces a team to reason through a problem and reach a decision that sticks. The shape of one depends on org size: a startup needs one or two pages, an enterprise needs more structure. A user notification system RFC serves as the running example here, because it shows both what the format buys and where it goes wrong.</p>
<p><strong>Why RFCs Matter</strong><...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build a URL Shortener with AWS CDK Part 3: Security & Custom Domains]]></title>
            <description><![CDATA[Implementing custom domains, bulk operations, URL expiration, and defense-in-depth security measures for production link shortener services.]]></description>
            <link>https://sph.sh/en/posts/aws-cdk-link-shortener-part-3/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-cdk-link-shortener-part-3/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[waf]]></category>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[rate-limiting]]></category>
            <category><![CDATA[custom-domains]]></category>
            <category><![CDATA[bulk-operations]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 05 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Building a production link shortener takes more than creating short URLs. It takes security measures that absorb legitimate scale while blocking abuse. Link shorteners are attractive targets for malicious actors who use them to distribute harmful content, bypass security filters, and run phishing campaigns.</p>
<p>Modern link shortener services need defense-in-depth protection combining input validation, rate limiting, authentication, and real-time monitoring. This approach protects both your service and the users who click shortened links.</p>
<p>In Part 1 and Part 2, we built the foundation and core redirect functionality. Now let's add the advanced features and security measures that separate a toy project from a production service.</p>
<p><strong>Custom Domain Configuration</strong>...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build a URL Shortener with AWS CDK Part 4: Production Deployment]]></title>
            <description><![CDATA[Multi-environment deployment, performance optimization at scale, cost management, and monitoring with solid incident response patterns.]]></description>
            <link>https://sph.sh/en/posts/aws-cdk-link-shortener-part-4/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-cdk-link-shortener-part-4/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[cloudfront]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[blue-green-deployment]]></category>
            <category><![CDATA[load-testing]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 05 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Production optimization is about predictable performance under any load condition, not raw speed alone. Infrastructure that behaves perfectly in staging can still reveal scaling bottlenecks the first time traffic spikes.</p>
<p>The most common oversight is provisioning the database for steady-state traffic instead of peak load. A DynamoDB table sized for normal operations becomes a bottleneck when traffic increases 10x during a campaign or a product launch.</p>
<p>Parts 1-3 covered the foundation, core functionality, and security. What follows is the production layer: four environments, Lambda and DynamoDB tuning, cost controls, blue-green deployment, and the metrics worth alerting on.</p>
<p><strong>Multi-Environment Deployment: Beyond Dev and Prod</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS CDK URL Shortener Part 5: Scaling & Long-term Maintenance]]></title>
            <description><![CDATA[Multi-region deployment, database scaling, disaster recovery, and long-term maintenance patterns for production systems at scale.]]></description>
            <link>https://sph.sh/en/posts/aws-cdk-link-shortener-part-5/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-cdk-link-shortener-part-5/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[multi-region]]></category>
            <category><![CDATA[disaster-recovery]]></category>
            <category><![CDATA[database-scaling]]></category>
            <category><![CDATA[maintenance]]></category>
            <category><![CDATA[operational-excellence]]></category>
            <category><![CDATA[capacity-planning]]></category>
            <category><![CDATA[global-deployment]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Fri, 05 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A single-region CDK link shortener starts to show latency and availability gaps once traffic spreads across geographies. The DynamoDB table also turns into a single point of failure. Without multi-region replication and a disaster recovery plan, one regional outage takes the entire redirect service down. The default worth reaching for is DynamoDB Global Tables behind a global CloudFront distribution, with Route 53 failover routing as the escape hatch when a region goes dark.</p>
<p>Part 1 covered the foundational link shortener infrastructure. This part scales it globally and adds the operational layer that keeps it running: capacity planning, backup automation, and maintenance of the technical debt a long-lived system accumulates.</p>
<p><strong>Multi-Region Architecture</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[API Versioning with AWS API Gateway and CDK: A Practical Guide]]></title>
            <description><![CDATA[Path-based API versioning on AWS API Gateway with CDK: a lifecycle registry per version, per-version Lambda handlers, discovery, and deprecation signals.]]></description>
            <link>https://sph.sh/en/posts/api-versioning-aws-cdk-complete-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/api-versioning-aws-cdk-complete-guide/</guid>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[versioning]]></category>
            <category><![CDATA[case-study]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Path-based versioning (/v1/users, /v2/users) backed by an explicit lifecycle record per version is the default worth reaching for on API Gateway. It costs more infrastructure than one continuously evolving endpoint, and it is the approach that survives clients who cannot update on your schedule.</p>
<p>API evolution creates a standing conflict: the contract has to improve while existing integrations keep working. That conflict sharpens with enterprise clients, whose release cadences run from weekly deploys to update windows measured in quarters, and whose integrations are often embedded in systems nobody wants to touch. The CDK shape of the default is a version registry, per-version Lambda handlers, a discovery endpoint, and the deprecation signals that make an eventual sunset possible....</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Auth0 vs Firebase Auth vs Cognito vs Supabase Auth: Which to Choose]]></title>
            <description><![CDATA[Compare Auth0, Firebase Auth, Supabase Auth, AWS Cognito, and custom JWT: which to default to, how the pricing models differ, and the pitfalls to plan for.]]></description>
            <link>https://sph.sh/en/posts/auth-providers-mobile-web-api-comparison/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/auth-providers-mobile-web-api-comparison/</guid>
            <category><![CDATA[auth0]]></category>
            <category><![CDATA[cognito]]></category>
            <category><![CDATA[firebase]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[authentication]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Authentication provider choice sets development velocity, compliance ceiling, and monthly bill for years. Swapping providers later is one of the riskiest migrations a product can run. The default worth starting from is the managed provider already native to your primary platform: Firebase Auth for mobile-first consumer apps, Supabase Auth for PostgreSQL-backed products, Cognito inside AWS-native serverless stacks, and Auth0 when SAML/SSO and compliance artifacts are contract requirements. Custom JWT stays a learning exercise.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Auth0 Multiple Audiences: Token Management in Micro Frontends]]></title>
            <description><![CDATA[Auth0 multi-audience authentication across micro frontends: token management strategies and silent authentication in React Native with WebView-based apps.]]></description>
            <link>https://sph.sh/en/posts/auth0-multi-audience-micro-frontends-token-management/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/auth0-multi-audience-micro-frontends-token-management/</guid>
            <category><![CDATA[auth0]]></category>
            <category><![CDATA[jwt]]></category>
            <category><![CDATA[oauth]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[security]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>An Auth0 access token carries exactly one audience. A micro frontend estate that talks to three separate APIs therefore needs three tokens, and the naive reading of that constraint is three logins. It does not have to be. Keep a single Auth0 session in the shell application. The shell trades that session for a per-audience access token on demand and passes tokens to each micro frontend over an origin-checked postMessage channel. The alternatives (one Auth0 application per micro frontend, one wide audience with fine-grained scopes, a server-side exchange service) all cost more and give back less.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Auth0 React Native Session Management with Biometrics]]></title>
            <description><![CDATA[A step-by-step guide to secure session management in production React Native apps with Auth0, biometric authentication, and proper token lifecycle handling.]]></description>
            <link>https://sph.sh/en/posts/auth0-react-native-session-management-biometrics/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/auth0-react-native-session-management-biometrics/</guid>
            <category><![CDATA[auth0]]></category>
            <category><![CDATA[biometrics]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Session management in mobile applications presents challenges that web developers rarely encounter. A mobile app has to handle background states, network interruptions, biometric authentication, and platform-specific security constraints while keeping the experience seamless. The default that holds up across React Native apps: let the Auth0 tenant own refresh token rotation, keep tokens in Keychain or Keystore behind a biometric gate, and refresh preemptively instead of reacting to a 401.</p>
<p>The pieces are a token manager that refreshes on a timer and on network recovery, a biometric gate over platform secure storage, an app-state hook that enforces idle timeout, and an auth context that ties them together. Each step builds on the previous one, so the code is meant to be read in ord...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS CDK Link Shortener Part 1: Project Setup & Basic Infrastructure]]></title>
            <description><![CDATA[Set up a production-grade link shortener with AWS CDK, DynamoDB, and Lambda: architecture decisions, project layout, and the schema choices that hold up at scale.]]></description>
            <link>https://sph.sh/en/posts/aws-cdk-link-shortener-part-1/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-cdk-link-shortener-part-1/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[url-shortener]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Building a link shortener on AWS looks straightforward until redirect latency, URL validation, and per-link analytics become hard requirements. At millions of redirects per month, each architectural choice (storage engine, caching layer, CDN placement) affects both cost and tail latency.</p>
<p>The default that holds up is a single DynamoDB table, one small Lambda per route, and CloudFront in front of the redirect path. Get the project layout and the table schema right on day one, and the later parts of the series stay additive instead of turning into migrations. Redirect loops, malicious URLs, and abuse traffic are not late-stage concerns either: they decide what the create handler validates before it writes anything.</p>
<p><strong>Architecture Overview</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS CDK Link Shortener Part 2: Core Functionality & API Development]]></title>
            <description><![CDATA[Build the redirect engine, analytics collection, and API Gateway config: performance optimizations and debugging strategies for millions of daily redirects.]]></description>
            <link>https://sph.sh/en/posts/aws-cdk-link-shortener-part-2/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-cdk-link-shortener-part-2/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[analytics]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[redirect]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[debugging]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A link shortener is mostly a redirect engine. The short-code lookup and the HTTP 301 response are the only operations on the critical latency budget, and both have to stay under the user-perceived-instant threshold (around 200ms) even at high concurrency. The business logic around that hot path (analytics, rate limiting, link expiration, custom slugs) must not block the redirect; every feature added to the redirect handler directly costs latency at the edge.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Fargate 101: Serverless Containers Explained]]></title>
            <description><![CDATA[A practical guide to AWS Fargate: task definitions, awsvpc networking, cost trade-offs, and when serverless containers beat managing EC2 hosts yourself.]]></description>
            <link>https://sph.sh/en/posts/aws-fargate-101-serverless-containers/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-fargate-101-serverless-containers/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[fargate]]></category>
            <category><![CDATA[ecs]]></category>
            <category><![CDATA[containers]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Running containers on EC2 means managing OS patches, cluster capacity, and host-level failures. That work scales with the number of hosts, not with application complexity. AWS Fargate takes the layer away: you hand AWS a container image and a CPU/memory pair, and it runs the task on infrastructure you never log into.</p>
<p>For new containerized workloads, Fargate is the sensible default. You pay a premium per unit of compute and stop maintaining hosts in exchange. The cases where that trade goes the wrong way are specific: GPU workloads, privileged containers, and steady high utilization that Reserved Instances or Spot already cover. Knowing which side of that line a workload falls on is most of the decision.</p>
<p><strong>How to Think About Fargate</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Fargate Production Patterns: Cost, Monitoring, Blue-Green]]></title>
            <description><![CDATA[Advanced Fargate patterns learned from running production workloads. From cost optimization to stateful containers, here's what the docs won't tell you.]]></description>
            <link>https://sph.sh/en/posts/aws-fargate-102-advanced-patterns/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-fargate-102-advanced-patterns/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[fargate]]></category>
            <category><![CDATA[ecs]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <category><![CDATA[efs]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[patterns]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In Fargate 101, we covered the basics of getting started. The patterns below come from what breaks after that: the bill, the parts of the infrastructure Fargate hides from you, and deployments that need a way back. Cost optimization, stateful containers, monitoring, and blue-green deploys, in that order.</p>
<p><strong>Cost Optimization Strategies</strong></p>
<p>As mentioned in the previous post, Fargate does cost more than EC2 for the same compute. Four levers close most of that gap: Spot capacity, right-sizing, ARM, and Savings Plans.</p>
<p><strong>Fargate Spot for Interruptible Work</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Fargate Troubleshooting: ENI Limits, Timeouts, Debugging]]></title>
            <description><![CDATA[Fargate failure modes that green dashboards hide: ENI quota exhaustion, subnet routing breaks, memory leaks, and the checks that find each one.]]></description>
            <link>https://sph.sh/en/posts/aws-fargate-103-production-lessons/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-fargate-103-production-lessons/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[fargate]]></category>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <category><![CDATA[incident-response]]></category>
            <category><![CDATA[production]]></category>
            <category><![CDATA[monitoring]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Fargate hides resource constraints (ENI limits, memory leaks, and subnet exhaustion) behind green dashboards until a traffic spike makes them impossible to ignore. These failures share a pattern: the root cause is invisible to standard CloudWatch metrics, so tasks stall or crash with misleading errors. Five failure modes account for most of them, and each one has a check that identifies it in minutes.</p>
<p>Previous parts of this Fargate series (101, 102) covered the basics and advanced patterns. The next part, 104, covers Infrastructure-as-Code deployment patterns.</p>
<p><strong>ENI Exhaustion</strong></p>
<p><strong>Symptom:</strong> Tasks never leave PENDING, and the service event log shows:
[code block]</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deploying AWS Fargate: CDK vs Terraform vs SAM]]></title>
            <description><![CDATA[How to deploy Fargate effectively with different IaC tools. Practical patterns, common gotchas, and what works best for each approach.]]></description>
            <link>https://sph.sh/en/posts/aws-fargate-104-iac-deep-dive/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-fargate-104-iac-deep-dive/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[fargate]]></category>
            <category><![CDATA[cdk]]></category>
            <category><![CDATA[terraform]]></category>
            <category><![CDATA[sam]]></category>
            <category><![CDATA[iac]]></category>
            <category><![CDATA[infrastructure-as-code]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Deploying Fargate services with CDK, Terraform, or SAM each produces working infrastructure, but the wrong choice creates maintenance overhead that compounds with every new service. For a new AWS-only service, CDK is the default: its high-level constructs collapse dozens of CloudFormation resources into a few lines. Terraform earns the switch when several teams share one change surface, or when a second cloud is realistic. SAM fits only when Fargate is a side note next to Lambda.</p>
<p>The differences rarely show up in the first deployment. They show up a year later, in how a change gets reviewed, how drift is detected, and how much code each new service costs to add.</p>
<p><strong>IaC Tool Comparison for Fargate</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Lambda Cold Start Optimization: Production Lessons Learned]]></title>
            <description><![CDATA[Production-tested strategies for cutting AWS Lambda cold starts: runtime selection, provisioned concurrency, and practical optimization techniques.]]></description>
            <link>https://sph.sh/en/posts/aws-lambda-guide-101-cold-start-optimization/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-lambda-guide-101-cold-start-optimization/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[cold-start]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[provisioned-concurrency]]></category>
            <category><![CDATA[war-stories]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Cold start latency lands on the user's clock. For a Lambda function behind an interactive API, the cheapest wins come from three places: the runtime you pick, the size of the deployment package, and what the initialization code does before the handler runs. Work through those three first.</p>
<p>Provisioned concurrency removes the init cost from the requests it covers, and for a checkout endpoint with a latency SLA it is the right answer. Traffic above the provisioned count spills onto on-demand environments and can still cold start, so the scaling policy matters as much as the reservation. It also changes the bill enough to deserve a cost calculation before it ships.</p>
<p><strong>Where Cold Starts Cost You</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Lambda Memory Allocation and Performance Tuning: The Complete Guide]]></title>
            <description><![CDATA[Tune AWS Lambda performance: the memory-to-CPU model, benchmarking with Power Tuning, cost analysis, and adaptive allocation patterns.]]></description>
            <link>https://sph.sh/en/posts/aws-lambda-guide-102-memory-performance-tuning/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-lambda-guide-102-memory-performance-tuning/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[memory-optimization]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <category><![CDATA[benchmarking]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>After optimizing cold starts in the first part, the next challenge is making your Lambda functions run efficiently once they are warm. Memory allocation is the configuration knob with the widest blast radius, because one value sets the CPU share, the execution time, and the bill at once.</p>
<p>The working default: start at 1024 MB, then move only where a measured curve tells you to. The 128 MB default is almost always wrong for anything that computes, and the reflex fix of jumping straight to 3008 MB usually buys capacity that single-threaded code cannot spend.</p>
<p>Memory size is only one input. The model worth carrying is the relationship between allocated memory, the CPU that comes with it, and the GB-seconds you are billed for.</p>
<p><strong>Lambda's Memory-CPU Model</strong></p...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Lambda Production Monitoring and Debugging: Proven Strategies]]></title>
            <description><![CDATA[Instrument AWS Lambda for production: CloudWatch custom metrics, X-Ray tracing, structured logging, and alerts that track business impact.]]></description>
            <link>https://sph.sh/en/posts/aws-lambda-guide-103-production-monitoring-debugging/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-lambda-guide-103-production-monitoring-debugging/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[cloudwatch]]></category>
            <category><![CDATA[x-ray]]></category>
            <category><![CDATA[observability]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Lambda functions fail silently in production when default CloudWatch metrics, cold-start noise, and missing distributed traces combine to obscure the root cause. Without a structured observability strategy, debugging shifts from data-driven analysis to guesswork under pressure. The setup that holds for most Lambda workloads is small: custom metrics keyed to business outcomes, X-Ray active tracing, and JSON logs carrying the request ID on every line.</p>
<p>Those three signals do different jobs. Metrics tell you that something broke, traces tell you where, and logs tell you why. Drop one and the other two leave a gap that surfaces at the worst moment: a payment handler that times out without logging an error, or a retry storm that never trips an alarm.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Lambda Cost Optimization: VPC, Layers, and Advanced Patterns]]></title>
            <description><![CDATA[Advanced AWS Lambda patterns and cost optimization: Lambda Layers, VPC configuration, cross-account execution, and architectural decisions.]]></description>
            <link>https://sph.sh/en/posts/aws-lambda-guide-104-advanced-patterns-cost-optimization/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-lambda-guide-104-advanced-patterns-cost-optimization/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <category><![CDATA[lambda-layers]]></category>
            <category><![CDATA[vpc]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[migration]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[war-stories]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Serverless bills rarely surprise anyone in the first month. They surprise people at scale. Over-provisioned memory, idle provisioned concurrency, and a monolithic handler each multiply the same invocation count month after month, and Lambda's per-invocation pricing makes all three cheap to make and expensive to keep.</p>
<p>Past a few dozen functions the questions change. How do you share dependencies without wrecking cold starts, when is VPC attachment worth its cost, how do you reach across accounts safely, and where does the bill actually go? The answer to the last one is unglamorous: audit memory and concurrency against CloudWatch data before touching anything else, because configuration drift usually costs more than code-level optimization recovers.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Middy Alternatives: Building a Custom AWS Lambda Middleware Framework]]></title>
            <description><![CDATA[When a Lambda fleet outgrows Middy's static middleware model, how a project-specific engine handles per-request config, and what owning one costs]]></description>
            <link>https://sph.sh/en/posts/aws-lambda-middleware-middy-custom-framework/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-lambda-middleware-middy-custom-framework/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[middleware]]></category>
            <category><![CDATA[custom-framework]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[architecture-patterns]]></category>
            <category><![CDATA[migration]]></category>
            <category><![CDATA[production-lessons]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Middy covers the typical middleware needs of a small Lambda fleet, but the tradeoffs of its generic middleware-chain model become measurable once a service hits about 50 functions sharing a common middleware stack: per-invocation overhead, cold-start cost of the middleware chain, and the coupling that a shared wrapper creates between otherwise unrelated functions. At that scale the question becomes whether to continue layering on top of Middy's abstractions, replace them with AWS Lambda Powertools, or build a project-specific middleware framework that only pays for the hooks the fleet actually uses.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Lambda Middleware with Middy - Clean Code and Best Practices]]></title>
            <description><![CDATA[Discover how Middy transforms Lambda development with middleware patterns, moving from repetitive boilerplate to clean, maintainable serverless functions]]></description>
            <link>https://sph.sh/en/posts/aws-lambda-middleware-middy-introduction/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-lambda-middleware-middy-introduction/</guid>
            <category><![CDATA[aws-lambda]]></category>
            <category><![CDATA[middy]]></category>
            <category><![CDATA[middleware]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[architecture-patterns]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Lambda handlers tend to grow the same preamble: parse the JSON body, validate it, catch errors, attach CORS and security headers. Every endpoint repeats it, and every copy drifts a little from the last one.</p>
<p>Middy is the pragmatic default for Node.js handlers behind API Gateway. It wraps a handler in composable middleware, so parsing, validation, error shaping, and headers live in one shared chain instead of in each function. The cost is an extra dependency and a little cold-start weight, which is why a few function shapes are still better off without it.</p>
<p><strong>What is Middy?</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Lambda Performance Optimization: Sub-10ms Latency]]></title>
            <description><![CDATA[Hold AWS Lambda warm-path latency inside a 10 ms budget with runtime choice, connection reuse, bundle discipline, caching, and memory tuning.]]></description>
            <link>https://sph.sh/en/posts/aws-lambda-millisecond-optimization-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-lambda-millisecond-optimization-guide/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[go]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[redis]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Latency-critical Lambda workloads are won or lost on the warm path. Cold starts get the attention, but a function that answers a request in single-digit milliseconds is mostly a story about which runtime loads your handler, how many network handshakes happen per invocation, and how much memory the function was given.</p>
<p>The default worth starting from: a compiled runtime on the hot path, clients created outside the handler, DynamoDB or ElastiCache behind it, and memory picked with AWS Lambda Power Tuning instead of guessed. Node.js can reach the same range, but it needs stricter bundle discipline to get there.</p>
<p><strong>Where the Milliseconds Go</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[S3 Presigned URLs for Large File Uploads with Lambda and CDK]]></title>
            <description><![CDATA[Handle large file uploads with S3 presigned URLs instead of Lambda proxies: a full CDK stack, both handlers, the browser side, and the security trade-offs.]]></description>
            <link>https://sph.sh/en/posts/aws-lambda-s3-signed-urls-large-file-uploads/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-lambda-s3-signed-urls-large-file-uploads/</guid>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Proxying file uploads through Lambda is the obvious first design, and it holds until the files get large. API Gateway caps a REST API request payload at 10 MB and Lambda caps a synchronous invocation payload at 6 MB, so a 2 GB video never reaches the function. Even when the payload fits, Lambda is billed for every second the transfer is in flight.</p>
<p>S3 presigned URLs are the better default above a few megabytes. Lambda signs a URL and returns it, the client PUTs the bytes straight to S3, and an S3 event notification wakes a processing function once the object lands. The trade-off is that everything Lambda used to enforce mid-stream (file type, size limits, virus scanning) has to move to signing time or to the event handler afterwards.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS Lambda with TypeScript: Production Best Practices]]></title>
            <description><![CDATA[Moving from Express.js to Lambda: the common mistakes teams make along the way, and the TypeScript patterns that reduce AWS bills at scale.]]></description>
            <link>https://sph.sh/en/posts/aws-serverless-typescript-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/aws-serverless-typescript-guide/</guid>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[cost-optimization]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[production]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A traditional Express.js API on EC2 delivers fixed costs, predictable scaling, and 99.9% uptime. The case for Lambda is usually triggered by a specific mismatch: a feature that needs to process 50,000 webhooks in under 10 minutes, once per month.</p>
<p>Keeping EC2 instances running 24/7 for a 10-minute monthly spike is wasteful, and Lambda removes that idle cost. What it adds is a different set of failure modes: an account-wide concurrency limit, cold starts on the critical path, and a billing model where one careless DynamoDB Scan costs more than all the compute around it.</p>
<p><strong>Common Objections to Serverless</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Bus Factor in Engineering Teams: How to Reduce Knowledge Risk]]></title>
            <description><![CDATA[Protect your team from single points of failure through knowledge distribution, documentation strategies, and systematic risk management.]]></description>
            <link>https://sph.sh/en/posts/bus-factor-knowledge-management-engineering-teams/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/bus-factor-knowledge-management-engineering-teams/</guid>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[documentation]]></category>
            <category><![CDATA[knowledge-sharing]]></category>
            <category><![CDATA[risk-management]]></category>
            <category><![CDATA[engineering-culture]]></category>
            <category><![CDATA[war-stories]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[mentorship]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>When critical knowledge about a system lives in a single person's head, that person becomes a single point of failure. This is the bus factor risk. It bites hardest because the knowledge is rarely written down. Payment flows, fraud-detection quirks, and the deploy steps everyone leaned on walk out with the engineer, and recovery slows down at exactly the moment an incident hits. The remedy that holds up is narrow: document the critical paths first, then prove the documentation works by handing it to someone who did not build the system.</p>
<p><strong>Understanding the Bus Factor</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Circuit Breaker Pattern: Building Resilient Microservices That Don't Cascade Failures]]></title>
            <description><![CDATA[Implementing the Circuit Breaker pattern in TypeScript: three states, timeout sizing against P99 latency, and threshold defaults per dependency type]]></description>
            <link>https://sph.sh/en/posts/circuit-breaker-pattern-resilient-microservices/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/circuit-breaker-pattern-resilient-microservices/</guid>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[patterns]]></category>
            <category><![CDATA[resilience]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A slow dependency is more dangerous than a dead one: requests that wait 30 seconds to time out exhaust thread pools and propagate failure upstream across otherwise healthy services. Without a containment mechanism, a single degraded downstream call can saturate an entire distributed system in seconds. The Circuit Breaker pattern contains that failure with three states and two numbers: the request timeout, and the error rate that trips the breaker. The timeout matters most: two to three times your P99 latency, rather than whatever the client library ships as a default. Sized that way, the breaker trips before the thread pool drains.</p>
<p><strong>The Problem: When Slow is Worse Than Dead</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[CQRS with AWS Lambda, EventBridge, and DynamoDB]]></title>
            <description><![CDATA[A practical CQRS implementation with AWS Lambda, EventBridge, and DynamoDB, covering event sourcing, eventual consistency, and distributed debugging.]]></description>
            <link>https://sph.sh/en/posts/cqrs-pattern-serverless-nodejs/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/cqrs-pattern-serverless-nodejs/</guid>
            <category><![CDATA[cqrs]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[event-sourcing]]></category>
            <category><![CDATA[eventbridge]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>CQRS (Command Query Responsibility Segregation) separates write operations (commands) from read operations (queries). Instead of one model serving both, each side gets a shape that fits its job: the write side validates and stores, the read side answers questions quickly.</p>
<p>Reach for the split when the two workloads have genuinely different shapes: a read-to-write ratio of 10:1 or higher, different latency budgets, or query patterns that a write-optimized schema cannot serve. The setup below uses one DynamoDB table per side, a separate Lambda function per path, and EventBridge carrying the events that keep the read model current. When reads and writes look alike, the pattern only adds moving parts.</p>
<p><strong>The Core Principle</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Choose a Database: SQL vs NoSQL vs NewSQL vs Edge]]></title>
            <description><![CDATA[Choose the right database across SQL, NoSQL, NewSQL, and edge options: the trade-offs of each category, selection criteria, and a decision framework.]]></description>
            <link>https://sph.sh/en/posts/database-selection-comprehensive-guide/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/database-selection-comprehensive-guide/</guid>
            <category><![CDATA[database]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[mysql]]></category>
            <category><![CDATA[mongodb]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[scalability]]></category>
            <category><![CDATA[production]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Choosing the wrong database engine for a workload forces expensive migrations later: a product catalog that runs fine on MongoDB at 1,000 records can degrade to full-collection scans at 100,000 without proper schema and index design. The mismatch between data model and access pattern is the root cause, not the database itself. SQL, NoSQL, NewSQL, and edge database categories each carry concrete trade-offs that point to the right choice before the first migration becomes necessary.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dead Letter Queue Strategies: Production-Ready Patterns for Resilient Event-Driven Systems]]></title>
            <description><![CDATA[Production-ready DLQ patterns for event-driven systems: monitoring, circuit breakers, exponential backoff, recovery, and the anti-patterns worth avoiding.]]></description>
            <link>https://sph.sh/en/posts/dead-letter-queue-production-strategies/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/dead-letter-queue-production-strategies/</guid>
            <category><![CDATA[azure]]></category>
            <category><![CDATA[circuit-breaker]]></category>
            <category><![CDATA[dead-letter-queue]]></category>
            <category><![CDATA[dlq]]></category>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[gcp]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[recovery]]></category>
            <category><![CDATA[reliability]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Dead Letter Queues hold messages that a consumer cannot process after its retry budget is exhausted. Without a DLQ, a poison pill either blocks the primary queue at head-of-line or silently disappears with the failed handler; either outcome loses both the event and the operational signal that something went wrong. The DLQ is a separation of concerns between "messages to process" and "messages that need human or tooling intervention", and it only works when the retry policy, alerting, and replay tooling around it are designed alongside.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Documentation as Infrastructure: Scaling Knowledge Across Engineering Teams]]></title>
            <description><![CDATA[Documentation debt can slow teams faster than technical debt. A guide to treating docs as critical infrastructure and scaling knowledge across engineering teams.]]></description>
            <link>https://sph.sh/en/posts/documentation-as-infrastructure-scaling-knowledge/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/documentation-as-infrastructure-scaling-knowledge/</guid>
            <category><![CDATA[documentation]]></category>
            <category><![CDATA[rfc]]></category>
            <category><![CDATA[adr]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[knowledge-management]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[organizational-design]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Documentation debt slows engineering teams in ways that look fine until the wrong person leaves. The cost surfaces during a payment incident, a migration, or an audit: the written procedure exists but the deep system understanding doesn't. The documentation that survives that moment has three layers: decisions captured as RFCs and ADRs, system reference generated from the systems themselves, and process docs written from real examples. Everything else is tooling and the review habits that keep those three layers current.</p>
<p><strong>Common Documentation Patterns</strong></p>
<p>Some recurring challenges in documentation appear across many organizations:</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kafka vs SQS vs EventBridge: Choosing Event-Driven Tools]]></title>
            <description><![CDATA[A deep dive into event-driven tools: Kafka, SQS, and EventBridge, message delivery patterns, DLQ strategies, and their AWS, Azure, and GCP equivalents.]]></description>
            <link>https://sph.sh/en/posts/event-driven-systems-tools-comparison/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/event-driven-systems-tools-comparison/</guid>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[azure]]></category>
            <category><![CDATA[dlq]]></category>
            <category><![CDATA[edge-computing]]></category>
            <category><![CDATA[eventbridge]]></category>
            <category><![CDATA[gcp]]></category>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[rabbitmq]]></category>
            <category><![CDATA[sns]]></category>
            <category><![CDATA[sqs]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Choosing an event-driven tool is less about hype and more about matching three shapes: a queue, a fan-out topic, or a router. Start with the managed primitive your cloud already gives you for that shape. Move to Kafka only when you need event replay, ordering across a partitioned key space, or throughput a managed queue quota cannot cover.</p>
<p>The rest of the decision comes down to delivery guarantees, message size limits, and what happens to a message nobody can process.</p>
<p><strong>Message Patterns: The Foundation</strong></p>
<p>Each pattern carries different tools and trade-offs:</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Frontend Build Tools History: From jQuery and Grunt to Bower]]></title>
            <description><![CDATA[Before webpack, we concatenated files with Grunt and wrestled with jQuery spaghetti. How frontend tooling grew from manual file management to real build systems.]]></description>
            <link>https://sph.sh/en/posts/frontend-tooling-evolution-101-the-early-web-era/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/frontend-tooling-evolution-101-the-early-web-era/</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[jquery]]></category>
            <category><![CDATA[grunt]]></category>
            <category><![CDATA[bower]]></category>
            <category><![CDATA[frontend-history]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Frontend tooling began as a concatenate-and-minify problem: hand-managed script order, shell scripts for deployment, and the first wave of task runners (Make, then Grunt, then Gulp) as teams outgrew the manual process. The pressure on this early tooling came not from build speed but from dependency ordering and cross-browser compatibility: the difference between a site that loaded and one that produced silent JavaScript errors.</p>
<p>The story starts in the pre-build era (1995-2006) of hand-written scripts and browser-specific hacks, moves through jQuery's arrival as a compatibility layer, and ends in 2012 with Bower, the first taste of real package management on the frontend.</p>
<p><strong>The Pre-jQuery Dark Ages (1995-2006)</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Grunt vs Gulp vs Webpack: How Modern Bundling Emerged]]></title>
            <description><![CDATA[How Grunt reshaped build automation and webpack changed how we think about dependencies: the hard shift from manual processes to modern bundling.]]></description>
            <link>https://sph.sh/en/posts/frontend-tooling-evolution-102-task-runners-and-bundlers/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/frontend-tooling-evolution-102-task-runners-and-bundlers/</guid>
            <category><![CDATA[grunt]]></category>
            <category><![CDATA[gulp]]></category>
            <category><![CDATA[webpack]]></category>
            <category><![CDATA[browserify]]></category>
            <category><![CDATA[commonjs]]></category>
            <category><![CDATA[amd]]></category>
            <category><![CDATA[build-tools]]></category>
            <category><![CDATA[lessons-learned]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Manual shell-script build pipelines break silently across machines, accumulate tribal knowledge, and collapse when the one developer who understands them moves on. Undocumented build failures block deployments and make the toolchain itself a source of risk. Grunt answered that with declarative task automation, and Gulp replaced its configuration blocks with code streams. Browserify and webpack then moved the model from task orchestration to dependency-graph bundling.</p>
<p>Grunt arrived into that environment as a clear step forward. For the first time, teams had a tool that could automate the boring, error-prone processes while being configurable enough to handle complex projects. But as with every tool in this series, Grunt solved one set of problems while revealing entirely new ones....</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[esbuild, SWC, and Vite: Why Native Build Tools Replaced Webpack]]></title>
            <description><![CDATA[How native tools like esbuild, SWC, and Vite solved webpack's speed problems, taking builds from tens of seconds down to milliseconds.]]></description>
            <link>https://sph.sh/en/posts/frontend-tooling-evolution-103-performance-revolution/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/frontend-tooling-evolution-103-performance-revolution/</guid>
            <category><![CDATA[esbuild]]></category>
            <category><![CDATA[swc]]></category>
            <category><![CDATA[vite]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[go]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[webpack]]></category>
            <category><![CDATA[parcel]]></category>
            <category><![CDATA[build-tools]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>JavaScript-based bundlers like webpack hit a hard performance ceiling: single-threaded execution, per-file AST parsing, and garbage collection pauses pushed production builds past 60 seconds on large React apps. Slow feedback loops break developer focus and inflate CI costs at every merge. esbuild, SWC, and Vite broke through that ceiling by moving the hot path into native compiled code.</p>
<p>As teams grew larger, build times became bottlenecks for everything: local development, CI/CD pipelines, and deployment processes. Development teams spent more time waiting for builds than actually writing code.</p>
<p>That pressure produced the performance revolution in frontend tooling: a shift away from JavaScript-based tools toward native compiled ones.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Future Landscape: Edge Computing and Beyond]]></title>
            <description><![CDATA[What edge computing, AI-assisted development and platform integration actually changed in frontend tooling, and which promises the published figures do not support.]]></description>
            <link>https://sph.sh/en/posts/frontend-tooling-evolution-104-the-future-landscape/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/frontend-tooling-evolution-104-the-future-landscape/</guid>
            <category><![CDATA[edge-computing]]></category>
            <category><![CDATA[ai-development]]></category>
            <category><![CDATA[vercel]]></category>
            <category><![CDATA[cloudflare]]></category>
            <category><![CDATA[deno]]></category>
            <category><![CDATA[bun]]></category>
            <category><![CDATA[future]]></category>
            <category><![CDATA[deployment]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Frontend build tooling has crossed a threshold where bundler speed is no longer the limiting factor for developer experience; the next frontier is how build tooling interacts with where and how the code will run. Edge runtimes, deployment platforms, and framework-level primitives (React Server Components, streaming SSR, partial hydration) now influence the build pipeline as much as the bundler does. The boundary between "build" and "deploy" is dissolving into a single pipeline that targets a set of runtimes rather than a single server.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Git Branching Strategies: Real-World Lessons for Different Teams and Products]]></title>
            <description><![CDATA[Git branching strategies mapped to team size, product type, and release cadence. GitHub Flow is the default; here is when another model earns its overhead.]]></description>
            <link>https://sph.sh/en/posts/git-branching-strategies-real-world/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/git-branching-strategies-real-world/</guid>
            <category><![CDATA[git]]></category>
            <category><![CDATA[branching]]></category>
            <category><![CDATA[war-stories]]></category>
            <category><![CDATA[team-management]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[ci-cd]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[deployment]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Choosing the wrong Git branching strategy for your team size and release cadence causes coordination overhead, broken main branches, and blocked deployments. The mismatch compounds as teams grow: a strategy tuned for three developers adds unnecessary friction at twenty-five, and vice versa. For most teams the right default is GitHub Flow: short feature branches, one pull request gate, merge to main. The other four models each earn their overhead only under a specific constraint, whether that is continuous deploys, QA approval gates, environment schedules, or compliance audit trails.</p>
<p><strong>Five Production-Relevant Strategies</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Closure Compiler: Legacy, Lessons, and Modern Alternatives]]></title>
            <description><![CDATA[How Google's 2009 Closure Compiler shaped modern web tooling, from dead code elimination to type checking, and its lasting mark on today's build tools.]]></description>
            <link>https://sph.sh/en/posts/google-closure-compiler-legacy-modern-web-development/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/google-closure-compiler-legacy-modern-web-development/</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[closure-compiler]]></category>
            <category><![CDATA[build-tools]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[google]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[webpack]]></category>
            <category><![CDATA[esbuild]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[legacy-tech]]></category>
            <category><![CDATA[toolchain-evolution]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Google's Closure Compiler brought whole-program static analysis to JavaScript in 2009: cross-file dead code elimination and JSDoc-based type checking. That was years before webpack, esbuild, or TypeScript made those capabilities mainstream. Teams unaware of the lineage keep rediscovering the same ideas and crediting them to newer tools. Some of Closure's innovations survived into today's build toolchain, others did not, and the difference explains why parts of the modern stack look the way they do.</p>
<p><strong>What Made Closure Compiler Significant</strong></p>
<p>The Closure Compiler wasn't just another minifier. It was a complete static analysis engine that could understand your JavaScript code at a level that seemed almost magical for its time.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mermaid Diagram Types: Examples of Every Chart Type]]></title>
            <description><![CDATA[Every Mermaid diagram type with a copy-ready example: flowcharts, sequence and class diagrams, ER models, Gantt charts, journeys, and more]]></description>
            <link>https://sph.sh/en/posts/mermaid-diagram-showcase/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/mermaid-diagram-showcase/</guid>
            <category><![CDATA[mermaid]]></category>
            <category><![CDATA[diagrams]]></category>
            <category><![CDATA[documentation]]></category>
            <category><![CDATA[visualization]]></category>
            <category><![CDATA[charts]]></category>
            <category><![CDATA[graphs]]></category>
            <category><![CDATA[uml]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mermaid turns plain text into diagrams, so a chart can live in the same file as the code it documents. Every diagram type below comes with a working example you can copy and adapt. Click any diagram to open it fullscreen.</p>
<p><strong>1. Flowchart - Process Flow Visualization</strong></p>
<p>Flowcharts are perfect for visualizing processes, decision trees, and system flows.</p>
<p>The styled nodes below use a single neutral fill (#1e293b) and carry meaning in the stroke color, which keeps a dense diagram readable on a dark background.</p>
<p>[code block]</p>
<p><strong>2. Sequence Diagram - Interaction Timeline</strong></p>
<p>Perfect for documenting API calls, user interactions, and system communications.</p>
<p>[code block]</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Advanced Micro Frontend Patterns: Performance, Debugging, and Production Lessons]]></title>
            <description><![CDATA[Advanced micro frontend patterns: event-sourced shared state, bundle budgets, cross-app error tracing, CSP and signed messaging, and strangler-fig migration.]]></description>
            <link>https://sph.sh/en/posts/micro-frontends-advanced-patterns-debugging/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/micro-frontends-advanced-patterns-debugging/</guid>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[state-management]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Micro Frontend Series Navigation</strong></p>
<p>- <strong>Part 1</strong>: Architecture fundamentals and implementation types
- <strong>Part 2</strong>: Module Federation, communication patterns, and integration strategies
- <strong>Part 3 (You are here)</strong>: Advanced patterns, performance optimization, and production debugging</p>
<p><strong>Prerequisites</strong>: Part 1 fundamentals and Part 2 implementation patterns.</p>
<p>---</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Micro Frontend Architecture Fundamentals: From Monolith to Distributed Systems]]></title>
            <description><![CDATA[Micro frontend composition strategies compared: server-side, build-time, Module Federation, and iframe, with the team preconditions that make the trade pay off.]]></description>
            <link>https://sph.sh/en/posts/micro-frontends-architecture-fundamentals/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/micro-frontends-architecture-fundamentals/</guid>
            <category><![CDATA[react]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Micro frontend architectures split a single-page frontend into independently deployable, independently owned slices that compose at runtime. They solve a specific set of problems (team-size scaling, independent release cadence, technology flexibility) and introduce a corresponding set of new ones (runtime composition complexity, shared-dependency management, cross-slice state, cross-slice performance). The choice to adopt them is rarely a clean win; it is a trade of coordination overhead for release autonomy, and the trade only pays back at certain team sizes and product-structure boundaries.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Micro Frontend Implementation Patterns: Module Federation and Beyond]]></title>
            <description><![CDATA[Production-ready Module Federation setups, cross-app communication, routing strategies, and the race conditions that break split routing under load.]]></description>
            <link>https://sph.sh/en/posts/micro-frontends-implementation-patterns/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/micro-frontends-implementation-patterns/</guid>
            <category><![CDATA[module-federation]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[webpack]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Micro Frontend Series Navigation</strong></p>
<p>- <strong>Part 1</strong>: Architecture fundamentals and implementation types
- <strong>Part 2 (You are here)</strong>: Module Federation, communication patterns, and integration strategies
- <strong>Part 3</strong>: Advanced patterns, performance optimization, and production debugging</p>
<p><strong>Prerequisites</strong>: Part 1 covers the concepts assumed below. If you're new to micro frontends, start there.</p>
<p>---</p>
<p>Part 1 covered the fundamental architectural patterns for micro frontends. Module Federation is the dominant runtime integration approach, and the practical work lives in its setup, the communication patterns around it, and the debugging strategies production systems demand.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Replacing Factories and DI in Node.js with Pure Functions]]></title>
            <description><![CDATA[Factories, service layers, and DI containers rarely earn their keep in a Lambda-shaped Node.js service. What replaces them, and where classes still win.]]></description>
            <link>https://sph.sh/en/posts/microservices-to-serverless-functions-nodejs/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/microservices-to-serverless-functions-nodejs/</guid>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[functional-programming]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[refactoring]]></category>
            <category><![CDATA[war-stories]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Changing one validation rule in a Node.js payment service can eat an afternoon when the rule sits behind a factory, a dependency injection container, and a stack of interfaces with one implementation each. The cost is the distance between the change you want and the file that holds it. Xia and colleagues measured where that time goes in a field study for IEEE Transactions on Software Engineering: seven projects, 79 professional developers, 3,244 working hours. Comprehension took 57.62% of the working day, navigation another 23.96%, and editing 5.02%.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Re.Pack and Rspack for Cross-Platform Micro Frontends]]></title>
            <description><![CDATA[Patterns for shipping micro frontends across mobile, web, and desktop: performance, offline support, and production insights, with Rspack and Re.Pack approaches.]]></description>
            <link>https://sph.sh/en/posts/mobile-micro-frontends-multi-channel-production/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/mobile-micro-frontends-multi-channel-production/</guid>
            <category><![CDATA[expo]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[re-pack]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[rspack]]></category>
            <category><![CDATA[vite]]></category>
            <category><![CDATA[webview]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mobile-first micro frontend architectures rarely survive contact with a second channel unchanged. The assumptions that made the architecture work on mobile (WebView lifecycle, native bridge, native gestures, per-channel asset bundles) either do not map to web and desktop at all, or they map at a performance cost that erases the point of sharing the code in the first place. True multi-channel micro frontends (mobile, web, desktop Electron) require a runtime abstraction layer and a discipline around channel-specific code paths, not just "build once, deploy everywhere."</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mobile Micro Frontends with React Native Expo: WebView Architecture and Real-World Implementation]]></title>
            <description><![CDATA[How to implement micro frontend architecture in mobile apps with React Native Expo and WebViews, covering performance, proven patterns, and bundlers.]]></description>
            <link>https://sph.sh/en/posts/mobile-micro-frontends-react-native-expo-webviews/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/mobile-micro-frontends-react-native-expo-webviews/</guid>
            <category><![CDATA[expo]]></category>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[re-pack]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[rspack]]></category>
            <category><![CDATA[webview]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mobile teams increasingly face the challenge of integrating multiple web-based services from different teams, each with their own deployment cycles and tech stacks. Tight delivery timelines compound this complexity when web teams can't pause their development to assist with mobile integration.</p>
<p>The default worth reaching for is a native React Native shell that embeds each web app in its own WebView. Every web team keeps its own bundler and release cadence. The mobile side pays for that in memory pressure, bundle size, and navigation glue that has to be written by hand.</p>
<p><strong>Mobile Micro Frontend Series</strong></p>
<p>This is <strong>Part 1</strong> of the mobile micro frontends series:</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[React Native WebView Communication: postMessage Bridge Patterns]]></title>
            <description><![CDATA[WebView-to-native communication patterns: message passing, service integration, and a type-safe request-response bridge with working code.]]></description>
            <link>https://sph.sh/en/posts/mobile-micro-frontends-webview-communication-patterns/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/mobile-micro-frontends-webview-communication-patterns/</guid>
            <category><![CDATA[expo]]></category>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[module-federation]]></category>
            <category><![CDATA[re-pack]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[rspack]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[webview]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>WebView-to-native communication in a mobile micro frontend fails in three concrete ways: messages arrive out of order, TypeScript types diverge between the web and native sides, and JavaScript exceptions inside a WebView surface no stack trace in native crash reporters. Without a structured bridge layer these failures compound; a race condition in message passing can silently swallow payment confirmations or auth tokens.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Monolith to Microservices in Node.js: A Migration Guide]]></title>
            <description><![CDATA[A practical guide to evolving Node.js monoliths into event-driven serverless functions, with migration strategies and proven architectural patterns.]]></description>
            <link>https://sph.sh/en/posts/monolith-to-microservices-nodejs-evolution/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/monolith-to-microservices-nodejs-evolution/</guid>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[monolith]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[war-stories]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Monolith Maintainability Limits</strong></p>
<p>Node.js monoliths degrade predictably. A single shared deployment boundary lets one slow domain block the whole release cycle, and a peak-traffic failure in one feature takes the rest of the process down with it. The architectural fix is not refactoring the monolith; it is decomposing it into independently deployable, event-driven functions.</p>
<p>Boundaries decide the outcome. Pick them from how the code actually changes and fails, keep the system releasable while the split is in progress, and the migration stays reversible at every step.</p>
<p><strong>How the Monolith Grew</strong></p>
<p>An e-commerce platform of this shape usually begins as a straightforward Node.js Express application:</p>
<p>[code block]</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Multi-Account AWS Architecture: Event-Driven Systems at Scale]]></title>
            <description><![CDATA[Multi-account AWS architecture patterns for resilient event-driven systems: account structure, EventBridge routing, and cross-service communication.]]></description>
            <link>https://sph.sh/en/posts/multi-account-aws-event-driven-architecture/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/multi-account-aws-event-driven-architecture/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[eventbridge]]></category>
            <category><![CDATA[multi-account]]></category>
            <category><![CDATA[event-driven]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[iam]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Single-Account Architecture Limits</strong></p>
<p>Multi-account AWS architecture becomes essential when organizations reach certain scale and complexity thresholds. The shape worth defaulting to is one account per service team, a shared EventBridge bus between them, and event choreography instead of a central orchestrator.</p>
<p>Consider a multi-service platform with nine development teams deploying to the same AWS account. This works for small organizations, but it creates several critical problems as scale increases.</p>
<p><strong>Common Single-Account Anti-Patterns</strong></p>
<p>Multiple teams sharing a single AWS account often leads to resource conflicts, security issues, and operational complexity. Here's a typical anti-pattern configuration:</p>
<p>[code block]</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[OpenTelemetry in React Native: Building Production-Ready Observability]]></title>
            <description><![CDATA[Implementing OpenTelemetry in React Native with an OTLP collector, Firebase Performance, and Crashlytics for production-grade observability.]]></description>
            <link>https://sph.sh/en/posts/opentelemetry-react-native-firebase-observability/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/opentelemetry-react-native-firebase-observability/</guid>
            <category><![CDATA[firebase]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[react-native]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Many React Native teams ship without production visibility. Crashes that never reproduce locally, performance complaints with no timing data behind them, support tickets that describe a symptom but never the state the app was in.</p>
<p>A workable default: the OpenTelemetry JavaScript SDK inside the app, exporting over OTLP to a collector you run, with Firebase Performance and Crashlytics kept as a second signal. Vendor SDKs install faster, but the collector is what lets you swap backends later without shipping a new build, and it keeps APM credentials out of the app bundle.</p>
<p><strong>Mobile Observability Requirements</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Axios vs Fetch vs Undici: Node.js HTTP Clients Compared]]></title>
            <description><![CDATA[Why undici is the sensible default for Node.js server-to-server calls, and when Axios, native fetch, or Effect is the better pick]]></description>
            <link>https://sph.sh/en/posts/server-side-http-clients-comparison/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/server-side-http-clients-comparison/</guid>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[http-clients]]></category>
            <category><![CDATA[axios]]></category>
            <category><![CDATA[undici]]></category>
            <category><![CDATA[effect]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[circuit-breaker]]></category>
            <category><![CDATA[production]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Picking an HTTP client for Node.js server-to-server calls is rarely a one-time decision: native fetch, Axios, undici, and Effect each behave differently under connection pressure, retry storms, and partial failures. For a service that spends its day calling other services, undici is the default worth reaching for. It owns its connection pool, its timeouts are explicit, and it is the same engine native fetch already runs on.</p>
<p>The failure that costs the most is also the easiest to ship: native fetch with no timeout. Hanging connections hold Lambda concurrent executions open, so a slow upstream becomes a bill and an outage at once. Connection pooling, timeout semantics, and circuit-breaker support decide how a client behaves on its worst day; feature lists do not.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Serverless Framework vs AWS CDK: Should You Migrate? (Part 1)]]></title>
            <description><![CDATA[Why migrate from Serverless Framework to AWS CDK: licensing changes, architectural advantages, and when CDK becomes the better choice for your apps.]]></description>
            <link>https://sph.sh/en/posts/serverless-to-cdk-migration-part-1/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-to-cdk-migration-part-1/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[migration]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Serverless Framework and AWS CDK solve overlapping problems with different philosophies. Serverless Framework is a YAML-driven, provider-neutral abstraction over a Lambda-centric deployment unit; CDK is a typed, AWS-native synthesis layer over CloudFormation. When Serverless Framework introduced paid licensing, staying put stopped being the cost-free default, and the comparison became worth making again.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating from Serverless Framework to AWS CDK: Part 2 - Setting Up Your CDK Environment]]></title>
            <description><![CDATA[Structure a CDK project for serverless apps, configure TypeScript for Lambda, and set patterns that ease migration from Serverless Framework.]]></description>
            <link>https://sph.sh/en/posts/serverless-to-cdk-migration-part-2/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-to-cdk-migration-part-2/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A CDK project that works for one developer rarely survives contact with a team. The flat, single-stack layout from most tutorials breaks down once several engineers deploy Lambda functions across multiple environments at the same time. Shared stacks create merge conflicts, ownership gets blurry, and nobody agrees on where a new file belongs.</p>
<p>The fix is a project structure that isolates units of work so engineers can move in parallel, while keeping the conventions Serverless Framework users already know. For teams leaving Serverless Framework for CDK, this is the layout that scales from a first migration to a production platform.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating from Serverless Framework to AWS CDK: Part 3 - Lambda Functions and API Gateway]]></title>
            <description><![CDATA[Migrate Lambda functions, API Gateway, request validation, and error handling from Serverless Framework to AWS CDK with practical examples.]]></description>
            <link>https://sph.sh/en/posts/serverless-to-cdk-migration-part-3/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-to-cdk-migration-part-3/</guid>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[migration]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Lambda functions and API Gateway configurations are where migration complexity concentrates. A YAML-to-TypeScript conversion looks mechanical until bundling, memory sizing, and error handling each turn into a separate decision.</p>
<p>The pattern that holds up is a single shared NodejsFunction construct: runtime, architecture, memory, tracing, and bundling defaults live in one place, and every prop stays overridable for the functions that need something else. API Gateway gets the same treatment, with validators, authorizers, and integration responses defined once and reused across routes.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating from Serverless Framework to AWS CDK: Part 4 - Database and Environment Management]]></title>
            <description><![CDATA[Master DynamoDB migrations, environment variable management, secrets handling, and VPC configurations when moving from Serverless Framework to AWS CDK.]]></description>
            <link>https://sph.sh/en/posts/serverless-to-cdk-migration-part-4/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-to-cdk-migration-part-4/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[migration]]></category>
            <category><![CDATA[parameter-store]]></category>
            <category><![CDATA[rds]]></category>
            <category><![CDATA[secrets-manager]]></category>
            <category><![CDATA[vpc]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Database and environment management carry the most risk in a CDK migration. Unlike stateless Lambda functions, these components hold your application's persistent state and configuration. A wrong step deletes a table, loses data, or wires staging to a production key without any error message.</p>
<p>The safe path is narrow. Verify that a table exists before importing it, generate environment values from a type-safe builder that fails the synth when a required key is missing, and scope every secret to a single stage. The sections below apply that default to DynamoDB imports, Parameter Store, Secrets Manager, and VPC-attached functions.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating from Serverless Framework to AWS CDK: Part 5 - Authentication, Authorization, and IAM]]></title>
            <description><![CDATA[Implement robust authentication with Cognito, API Gateway authorizers, and fine-grained IAM policies when migrating from Serverless Framework to AWS CDK.]]></description>
            <link>https://sph.sh/en/posts/serverless-to-cdk-migration-part-5/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-to-cdk-migration-part-5/</guid>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[cognito]]></category>
            <category><![CDATA[iam]]></category>
            <category><![CDATA[jwt]]></category>
            <category><![CDATA[security]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Migrating authentication and authorization from Serverless Framework to AWS CDK presents unique challenges that can impact both security posture and application performance. Organizations often discover their Serverless Framework implementations have accumulated security debt through organic growth and rapid iteration cycles.</p>
<p>Common patterns include functions with overly broad IAM permissions, scattered authorization logic across multiple custom authorizers, and insufficient audit trails for access control decisions. These issues become apparent during migration assessments and can significantly impact compliance requirements.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating from Serverless Framework to AWS CDK: Part 6 - Migration Strategies and Best Practices]]></title>
            <description><![CDATA[Plan the Serverless Framework to AWS CDK cut-over: blue-green deployment, rollback procedures, infrastructure testing, and performance tuning.]]></description>
            <link>https://sph.sh/en/posts/serverless-to-cdk-migration-part-6/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/serverless-to-cdk-migration-part-6/</guid>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A CDK migration's final phase is not about the correctness of the generated infrastructure. It is about operational readiness during cut-over: a plan for taking traffic, a plan for reverting if the migration degrades production, and a plan for reconciling drift that appears under live load. For a migration covering dozens of Lambda functions and multiple DynamoDB tables, the cut-over plan is larger than the migration code itself.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[DynamoDB Toolbox Guide: Single-Table Design in TypeScript]]></title>
            <description><![CDATA[Move from raw AWS SDK complexity to production-ready single-table design with practical DynamoDB Toolbox patterns, common pitfalls, and scaling decisions.]]></description>
            <link>https://sph.sh/en/posts/streamline-dynamodb-toolbox-serverless-typescript/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/streamline-dynamodb-toolbox-serverless-typescript/</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[dynamodb-toolbox]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Building serverless APIs with raw DynamoDB SDK calls creates significant maintenance overhead. Thousands of lines of AttributeValue mappings, dozens of scattered UpdateExpression strings, and zero type safety lead to brittle systems. When schema changes accidentally corrupt user records, it becomes clear that a better approach is essential.</p>
<p>DynamoDB Toolbox is the default worth reaching for. It wraps a table in typed entity definitions, so key construction, defaults, and marshalling stop being hand-written at every call site. Entity design, a service layer that owns the business rules, and a staged migration off raw SDK calls are where most of the work sits.</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Feature Flags in React Native: A Stale-While-Revalidate Pattern]]></title>
            <description><![CDATA[Why synchronous feature flag loading stalls checkout, and how a stale-while-revalidate cache serves React Native flags instantly with background refresh.]]></description>
            <link>https://sph.sh/en/posts/swr-style-feature-flags-react-native/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/swr-style-feature-flags-react-native/</guid>
            <category><![CDATA[feature-flags]]></category>
            <category><![CDATA[mobile-development]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[swr]]></category>
            <category><![CDATA[war-stories]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Feature flags sit on the critical path of a checkout flow, so how you load them is an availability decision. A synchronous API call to fetch a flag adds a network round trip to every render. Under cold-start conditions that round trip can stretch into seconds, the checkout times out, and the user abandons the cart. Stale-while-revalidate takes the lookup off the critical path: the cached value is returned instantly while a fresh value is fetched in the background. Treat the cache as the default answer and the network as a background refresh, never as a gate.</p>
<p><strong>The Cost of Synchronous Flag Loading</strong></p>
<p>A basic feature flag system makes a synchronous API call to AWS Parameter Store every time a flag value is needed:</p>
<p>[code block]</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Holacracy vs Team Topologies: Team Autonomy Models Compared]]></title>
            <description><![CDATA[Practical structures and guardrails that raise team autonomy without chaos, drawing on Holacracy, the Spotify model, and Team Topologies.]]></description>
            <link>https://sph.sh/en/posts/team-autonomy-structures/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/team-autonomy-structures/</guid>
            <category><![CDATA[organization-design]]></category>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[team-topologies]]></category>
            <category><![CDATA[holacracy]]></category>
            <category><![CDATA[spotify-model]]></category>
            <category><![CDATA[dora]]></category>
            <category><![CDATA[conways-law]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Leadership announcements about "giving teams more autonomy" tend to land with mixed reactions. Autonomy without boundaries often creates more problems than it solves.</p>
<p>The common pattern is familiar. Moving from matrix project teams to product mode with stream-aligned teams produces velocity gains, along with unintended consequences: more defects, duplicated effort, teams stepping on each other's toes. Effective autonomy is not about removing constraints; it is about designing the right ones.</p>
<p>Team Topologies is the workable default here. Use its team types and interaction modes as the frame, then borrow role charters from Holacracy and community-of-practice vocabulary from the Spotify model. Context varies, but that combination lowers the trial-and-error cost of reorganizin...</p><p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Complete Markdown Typography Guide]]></title>
            <description><![CDATA[Every markdown typography and formatting feature, from headings and tables to footnotes, math blocks, and admonitions.]]></description>
            <link>https://sph.sh/en/posts/typography/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/typography/</guid>
            <category><![CDATA[documentation]]></category>
            <category><![CDATA[markdown]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[typography]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Markdown processors agree on headings, lists, and code fences. Beyond that, support diverges: footnotes, definition lists, math blocks, collapsible sections, and admonitions each depend on the renderer. The sections below show every feature with its source syntax, so you can check what survives in your own renderer before relying on it.</p>
<p><strong>Headings</strong></p>
<p><strong>Heading 1 Style (H1 = Post Title)</strong>
<strong>Heading 2 (H2)</strong>
<strong>Heading 3 (H3)</strong>
<strong>Heading 4 (H4)</strong>
<strong>Heading 5 (H5)</strong>
<strong>Heading 6 (H6)</strong></p>
<p><strong>Text Formatting</strong></p>
<p><strong>Basic Formatting</strong></p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Zod + OpenAPI + AWS Lambda: Schema-First API Development with CDK]]></title>
            <description><![CDATA[Keep a Lambda API and its OpenAPI contract in sync by generating the spec from Zod schemas and wiring that generation into CDK deployment.]]></description>
            <link>https://sph.sh/en/posts/zod-openapi-aws-lambda-cdk/</link>
            <guid isPermaLink="true">https://sph.sh/en/posts/zod-openapi-aws-lambda-cdk/</guid>
            <category><![CDATA[api-gateway]]></category>
            <category><![CDATA[aws-cdk]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[openapi]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[war-stories]]></category>
            <category><![CDATA[zod]]></category>
            <category><![CDATA[zod-validation]]></category>
            <dc:creator><![CDATA[sph.sh]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>When a Lambda API and its OpenAPI spec are maintained separately, schema drift is inevitable. A field added to Zod validation quietly goes missing from the contract clients generate code against, and downstream consumers break without warning. The fix is to keep one source of truth: wire Zod schemas directly into OpenAPI spec generation and CDK deployment, so the published contract cannot diverge from the validation that runs on every request.</p>
<p><strong>Documentation Drift: Business Cost</strong></p>
<p>The classic serverless API development problem is four different sources of truth diverging silently:</p>
<p>[code block]</p>
<p><em>Continue reading...</em></p>]]></content:encoded>
        </item>
    </channel>
</rss>