<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Billy Okeyo</title>
    <link>https://billyokeyo.dev/</link>
    <description>Articles on idempotency, transactions, sagas, CQRS, and building production-grade backend systems. A practical guide for engineers moving beyond CRUD.</description>
    <language>en</language>
    <lastBuildDate>Mon, 06 Jul 2026 09:09:26 +0000</lastBuildDate>
    <atom:link href="https://billyokeyo.dev/rss.xml" rel="self" type="application/rss+xml" />
    <managingEditor>billycartel360@gmail.com (Billy Okeyo)</managingEditor>
    <webMaster>billycartel360@gmail.com (Billy Okeyo)</webMaster>
    <copyright>© 2026 Billy Okeyo</copyright>




  
  





  



  


    <item>
      <title>Database Isolation Levels Explained: Why Two Transactions Can See Different Data</title>
      <link>https://billyokeyo.dev/posts/database-isolation-levels/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/database-isolation-levels/</guid>
      <pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-07-06T09:08:34+00:00</atom:updated>
  
      <description><![CDATA[Database isolation levels determine what one transaction can see while another transaction is still running. This article explains why isolation levels exist, the four SQL standard isolation levels, and how to choose the right one for your application.]]></description>
      <content:encoded><![CDATA[<blockquote>
  <p><em>“Transactions guarantee that your work completes correctly. Isolation levels determine what everyone else is allowed to see while that work is happening.”</em></p>
</blockquote>

<hr />

<p>In the previous article, we explored database transactions and learned how they ensure multiple database operations either succeed together or fail together. Transactions protect applications from partial execution, preventing situations where money is deducted from one account without being deposited into another or where inventory is reduced without successfully creating an order.</p>

<p>But transactions solve only part of the problem.</p>

<p>Modern applications rarely have just one user interacting with the database at a time. Thousands of customers may be placing orders, updating records, making payments, or querying reports simultaneously. Each of these actions runs inside its own transaction, and more often than not, several transactions are accessing the same data at exactly the same time.</p>

<p>This raises an important question:</p>

<p><strong>What should one transaction be allowed to see while another transaction is still running?</strong></p>

<p>Imagine opening your banking application to check your account balance.</p>

<p>At the exact same moment, your employer’s payroll system is depositing your monthly salary into the same account.</p>

<p>Should your transaction see the new balance immediately?</p>

<p>Should it continue seeing the old balance until the salary transaction finishes?</p>

<p>Should it wait until the payroll transaction completes before showing you anything at all?</p>

<p>Each answer is technically valid depending on how the database is configured.</p>

<p>Now imagine an online store where only one laptop remains in stock.</p>

<p>Customer A begins placing an order.</p>

<p>Before their transaction finishes, Customer B checks the product page.</p>

<p>Should Customer B still see one laptop available?</p>

<p>Should they see zero?</p>

<p>Should they wait until Customer A’s purchase either succeeds or fails?</p>

<p>Again, the answer depends on the database’s isolation level.</p>

<p>Isolation levels define the rules governing how concurrent transactions interact with one another. They determine whether one transaction can observe another transaction’s work before it has been completed, whether repeated reads always return the same result, and whether new rows appearing during a transaction should be visible immediately.</p>

<p>Although isolation levels are often introduced as an advanced database topic, they’re really about one simple idea:</p>

<blockquote>
  <p><strong>How much of another transaction’s work should your transaction be allowed to see?</strong></p>
</blockquote>

<p>The answer has significant consequences for both correctness and performance.</p>

<p>In this article, we’ll explore why isolation levels exist, the concurrency problems they solve, the four SQL standard isolation levels, and how to choose the right one for your application.</p>

<hr />

<h1 id="why-isolation-exists">Why Isolation Exists</h1>

<p>To understand isolation, imagine you’re reading a book in a library.</p>

<p>Halfway through chapter three, someone walks over, quietly replaces several pages with new ones, and walks away.</p>

<p>You continue reading without realizing anything changed.</p>

<p>The beginning of the chapter describes one story.</p>

<p>The ending describes another.</p>

<p>Nothing makes sense.</p>

<p>Databases can experience a remarkably similar problem.</p>

<p>When multiple transactions execute simultaneously, each transaction may be reading data while another transaction is actively changing it.</p>

<p>Without rules governing these interactions, applications could make decisions based on incomplete information, outdated values, or data that is eventually discarded.</p>

<p>Isolation exists to prevent these situations.</p>

<p>Rather than allowing every transaction unrestricted access to every change happening in the database, the database controls what each transaction can observe and when it can observe it.</p>

<p>Think of it as putting walls between transactions.</p>

<p>Some walls are very thin.</p>

<p>Transactions can see almost everything happening around them.</p>

<p>Other walls are much thicker.</p>

<p>Transactions operate almost as though they’re the only users of the database.</p>

<p>The thicker the wall, the more isolated the transaction becomes.</p>

<hr />

<h1 id="the-trade-off-between-consistency-and-performance">The Trade-Off Between Consistency and Performance</h1>

<p>At first glance, it might seem obvious that every database should simply use the highest possible isolation level.</p>

<p>After all, if stronger isolation produces more consistent data, why wouldn’t every system choose it?</p>

<p>The answer lies in performance.</p>

<p>Imagine a supermarket with only one checkout counter.</p>

<p>Every customer waits patiently in line.</p>

<p>Because only one cashier is serving customers, inventory updates happen one at a time.</p>

<p>Mistakes are rare.</p>

<p>Unfortunately, the queue becomes enormous.</p>

<p>Now imagine opening ten checkout counters.</p>

<p>Customers move much faster.</p>

<p>However, all ten cashiers are now updating the same inventory system simultaneously.</p>

<p>Keeping everything synchronized becomes much more difficult.</p>

<p>Databases face exactly the same challenge.</p>

<p>Higher isolation levels provide stronger guarantees about data consistency, but they often require additional locking, coordination, and waiting.</p>

<p>Lower isolation levels allow more transactions to execute concurrently, increasing throughput and reducing latency, but they also increase the likelihood that transactions observe changing data.</p>

<p>Isolation levels are therefore a balancing act between two competing goals:</p>

<ul>
  <li><strong>Consistency</strong>, ensuring every transaction sees predictable and reliable data.</li>
  <li><strong>Concurrency</strong>, allowing as many users as possible to interact with the system simultaneously.</li>
</ul>

<p>Different applications make different choices.</p>

<p>A banking application processing financial transfers typically prioritizes correctness over raw performance.</p>

<p>An analytics dashboard generating sales reports might tolerate slightly older data if it means thousands of users can run reports simultaneously without slowing the system.</p>

<p>Neither approach is universally correct.</p>

<p>The appropriate isolation level depends entirely on your business requirements.</p>

<hr />

<h1 id="concurrency-anomalies-the-problems-isolation-levels-exist-to-solve">Concurrency Anomalies: The Problems Isolation Levels Exist to Solve</h1>

<p>Isolation levels were not invented simply to make databases more complicated.</p>

<p>They exist because concurrent transactions can produce behaviors that most developers would consider surprising—or even dangerous.</p>

<p>These unexpected behaviors are collectively known as <strong>concurrency anomalies</strong>.</p>

<p>Every isolation level is essentially a trade-off between preventing these anomalies and maintaining good performance.</p>

<p>Before discussing the isolation levels themselves, it’s important to understand the problems they are designed to solve.</p>

<p>The four anomalies you’ll encounter most often are:</p>

<ul>
  <li>Dirty Reads</li>
  <li>Non-Repeatable Reads</li>
  <li>Phantom Reads</li>
  <li>Lost Updates</li>
</ul>

<p>Each represents a different way concurrent transactions can interfere with one another.</p>

<p>Let’s begin with the simplest.</p>

<hr />

<h1 id="dirty-reads">Dirty Reads</h1>

<p>Imagine Alice has <strong>KES 50,000</strong> in her account.</p>

<p>She initiates a transfer of <strong>KES 20,000</strong> to another account.</p>

<p>The banking system begins processing the transaction.</p>

<p>The first step deducts the money from Alice’s balance.</p>

<p>Before the transaction finishes, another process—perhaps an ATM balance inquiry or an online banking session—checks Alice’s account.</p>

<p>At that moment, it sees a balance of <strong>KES 30,000</strong>.</p>

<p>Everything seems perfectly normal.</p>

<p>Then something unexpected happens.</p>

<p>The transfer fails because the destination account no longer exists.</p>

<p>The database rolls back the transaction.</p>

<p>Alice’s balance immediately returns to <strong>KES 50,000</strong>.</p>

<p>The second transaction has now made a decision based on information that never officially existed.</p>

<p>It observed data that was eventually discarded.</p>

<p>This is known as a <strong>Dirty Read</strong>.</p>

<p>A dirty read occurs when one transaction reads data written by another transaction <strong>before that transaction has been committed</strong>.</p>

<p>The easiest way to understand it is to imagine reading someone’s unfinished draft before they’ve decided whether to keep or delete it.</p>

<p>The version you read may never become the final version.</p>

<p>Making business decisions based on that draft could lead to incorrect outcomes.</p>

<p>Fortunately, most modern relational databases prevent dirty reads by default because they are rarely desirable in business applications.</p>

<p>The SQL standard still defines them because they help explain the spectrum of isolation levels.</p>

<hr />

<h1 id="timeline-of-a-dirty-read">Timeline of a Dirty Read</h1>

<pre><code class="language-text">Transaction A                     Transaction B

BEGIN

Balance = 50,000

↓

Update Balance = 30,000

                               Read Balance = 30,000 ❌

↓

Transfer Fails

↓

ROLLBACK

Balance returns to 50,000
</code></pre>

<p>Transaction B has observed a value that disappeared moments later.</p>

<p>From the perspective of the database, that balance never officially existed.</p>

<p>Yet another transaction already acted as though it did.</p>

<p>This is precisely the type of inconsistency isolation levels are designed to prevent.</p>

<hr />
<h1 id="non-repeatable-reads">Non-Repeatable Reads</h1>

<p>Suppose you’re building an online banking application.</p>

<p>A customer opens the app and views their account balance. At that moment, the database reports a balance of <strong>KES 50,000</strong>.</p>

<p>The customer decides to transfer <strong>KES 40,000</strong> to another account, but before confirming the transfer, the application performs one final balance check to ensure sufficient funds are still available.</p>

<p>This seems like a perfectly reasonable workflow.</p>

<p>However, between the first balance check and the second, another transaction deposits <strong>KES 100,000</strong> into the same account.</p>

<p>When the application performs the second query, the balance is no longer <strong>KES 50,000</strong>.</p>

<p>It’s now <strong>KES 150,000</strong>.</p>

<p>Nothing is technically wrong.</p>

<p>The second transaction committed successfully.</p>

<p>The balance genuinely changed.</p>

<p>The surprising part is that <strong>the same transaction read the same row twice and received two different answers</strong>.</p>

<p>This phenomenon is known as a <strong>Non-Repeatable Read</strong>.</p>

<p>Unlike a dirty read, the second transaction isn’t reading uncommitted data. Every value it sees has been permanently committed to the database.</p>

<p>The inconsistency comes from the fact that another transaction modified the row while the first transaction was still running.</p>

<p>Imagine reading yesterday’s newspaper while someone keeps replacing pages with today’s edition.</p>

<p>The information isn’t incorrect.</p>

<p>It’s simply inconsistent because the document changed while you were reading it.</p>

<p>For many applications, this isn’t a problem.</p>

<p>If you’re refreshing a weather dashboard or checking the number of users currently online, it’s perfectly acceptable for values to change between two queries.</p>

<p>However, systems that rely on a stable snapshot of data—such as financial reporting, payroll processing, or end-of-day reconciliation—often require the same query to return the same result throughout the entire transaction.</p>

<hr />

<h1 id="timeline-of-a-non-repeatable-read">Timeline of a Non-Repeatable Read</h1>

<pre><code class="language-text">Transaction A                     Transaction B

BEGIN

Read Balance = 50,000

                               BEGIN

                               Deposit 100,000

                               COMMIT

Read Balance = 150,000 ❌

COMMIT
</code></pre>

<p>Transaction A never modified the balance itself.</p>

<p>It simply asked the same question twice and received two different answers because another committed transaction changed the underlying data in the meantime.</p>

<hr />

<h1 id="real-world-example-updating-a-customer-profile">Real-World Example: Updating a Customer Profile</h1>

<p>Consider an insurance application where a customer service representative opens a customer’s profile.</p>

<p>The representative spends several minutes reviewing the information before approving a policy update.</p>

<p>Meanwhile, another employee updates the customer’s phone number and address.</p>

<p>When the representative finally clicks <strong>Save</strong>, the application may now be working with information that is different from what was originally displayed.</p>

<p>Depending on how the application handles these changes, it could accidentally overwrite newer data or make decisions using outdated information.</p>

<p>This isn’t a database bug.</p>

<p>It’s simply the natural consequence of multiple users interacting with the same record at the same time.</p>

<p>Applications that require users to work with a consistent view of the data often use higher isolation levels or optimistic concurrency controls to detect these situations before committing changes.</p>

<hr />

<h1 id="phantom-reads">Phantom Reads</h1>

<p>Now let’s consider a different scenario.</p>

<p>Instead of reading a single row twice, imagine you’re querying an entire collection of rows.</p>

<p>Suppose you’re generating a report showing all loan applications submitted today.</p>

<p>Your first query returns:</p>

<pre><code class="language-text">Loan Applications Submitted Today

-------------------------------

Loan #101

Loan #102

Loan #103

Total: 3
</code></pre>

<p>While your report is still running, another loan application is submitted and committed to the database.</p>

<p>A few moments later, your transaction performs the exact same query again.</p>

<p>This time the results look different.</p>

<pre><code class="language-text">Loan Applications Submitted Today

-------------------------------

Loan #101

Loan #102

Loan #103

Loan #104

Total: 4
</code></pre>

<p>Notice what changed.</p>

<p>None of the existing rows were modified.</p>

<p>Instead, an entirely <strong>new row appeared</strong>.</p>

<p>This is called a <strong>Phantom Read</strong>.</p>

<p>A phantom read occurs when the same query returns a different set of rows because another transaction inserted, updated, or deleted records that match the query’s search criteria.</p>

<p>Think of it like counting the number of people in a room.</p>

<p>You count 20 people.</p>

<p>While you’re writing the number down, someone walks into the room.</p>

<p>You count again.</p>

<p>Now there are 21 people.</p>

<p>Nothing about the original twenty people changed.</p>

<p>The difference is that a new “phantom” appeared between your two observations.</p>

<hr />

<h1 id="timeline-of-a-phantom-read">Timeline of a Phantom Read</h1>

<pre><code class="language-text">Transaction A                     Transaction B

BEGIN

SELECT *

WHERE loan_date = TODAY

Returns 3 rows

                               BEGIN

                               INSERT Loan #104

                               COMMIT

SELECT *

WHERE loan_date = TODAY

Returns 4 rows ❌

COMMIT
</code></pre>

<p>Unlike a non-repeatable read, where an existing row changes, phantom reads involve the appearance or disappearance of entire rows.</p>

<hr />

<h1 id="why-phantom-reads-matter">Why Phantom Reads Matter</h1>

<p>Imagine you’re calculating today’s total revenue for financial reporting.</p>

<p>Your reporting transaction begins at 5:00 PM and starts aggregating sales.</p>

<p>While it’s still processing, new sales continue being recorded.</p>

<p>Different parts of the report may now be working with different datasets.</p>

<p>The total revenue calculated on page one might not match the detailed transaction list generated on page five because new rows appeared while the report was still executing.</p>

<p>In reporting systems, this can produce confusing and inconsistent results.</p>

<p>Higher isolation levels solve this problem by ensuring the transaction sees a consistent snapshot of the data throughout its lifetime, even if other transactions continue inserting new rows.</p>

<hr />

<h1 id="lost-updates">Lost Updates</h1>

<p>The final concurrency anomaly is perhaps the most dangerous because it silently discards valid work.</p>

<p>Imagine two warehouse employees looking at the same inventory record.</p>

<p>The system currently shows:</p>

<pre><code class="language-text">Laptop Stock = 10
</code></pre>

<p>Employee A sells one laptop.</p>

<p>Employee B also sells one laptop at almost exactly the same time.</p>

<p>Both employees read the current stock before making their update.</p>

<p>Each calculates the new quantity as:</p>

<pre><code class="language-text">10 - 1 = 9
</code></pre>

<p>Employee A saves.</p>

<p>The inventory becomes:</p>

<pre><code class="language-text">9
</code></pre>

<p>A fraction of a second later, Employee B saves.</p>

<p>The inventory is still:</p>

<pre><code class="language-text">9
</code></pre>

<p>One of the updates has effectively disappeared.</p>

<p>The correct inventory should now be <strong>8</strong>, but because both transactions started from the same original value, one update overwrote the other.</p>

<p>This is known as a <strong>Lost Update</strong>.</p>

<p>Unlike the previous anomalies, nothing appears obviously wrong.</p>

<p>No errors occur.</p>

<p>No constraints are violated.</p>

<p>The database happily accepts both updates.</p>

<p>The problem is that one user’s work has unintentionally replaced another’s.</p>

<p>Lost updates are one of the primary reasons databases provide row locking, optimistic concurrency control, and stronger isolation levels.</p>

<p>Without these protections, applications that receive many simultaneous updates—such as inventory systems, banking platforms, or booking applications—can slowly drift away from reality without anyone noticing.</p>

<p>Now that we understand the problems, the next question is obvious: How do databases prevent them? That’s exactly what we’ll cover in Part 2.”</p>

]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/database-isolation-levels.jpg" medium="image" />
  
  
    
      <category>Database</category>
    
      <category>Concurrency</category>
    
  
  
    
      <category>Database</category>
    
      <category>Concurrency</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Database Transactions Explained: Keeping Data Correct When Things Go Wrong</title>
      <link>https://billyokeyo.dev/posts/database-transactions-explained/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/database-transactions-explained/</guid>
      <pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-07-03T21:40:28+00:00</atom:updated>
  
      <description><![CDATA[Database transactions are a fundamental concept in database management systems that ensure data integrity and consistency. This article explains what database transactions are, why they are important, and how they work.]]></description>
      <content:encoded><![CDATA[<blockquote>
  <p><em>“Transactions are not just a database feature—they’re one of the fundamental building blocks of reliable software.”</em></p>
</blockquote>

<p>Imagine you’re transferring <strong>KES 10,000</strong> from your savings account to a friend.</p>

<p>From your perspective, it’s a single action. You tap <strong>“Send Money”</strong>, authenticate the transaction, and wait for confirmation. Behind the scenes, however, the banking system performs several independent operations. It verifies that you have sufficient funds, deducts the amount from your account, credits your friend’s account, records the transaction in a ledger, updates account balances, and generates a receipt.</p>

<p>Each of these operations is important, but together they represent a single business action: <strong>a money transfer</strong>.</p>

<p>Now imagine the server crashes immediately after deducting the money from your account but before crediting your friend.</p>

<p>The result is disastrous. Your balance has decreased, your friend never receives the money, and unless additional recovery mechanisms exist, the system is left in an inconsistent state. From a customer’s perspective, the money has simply disappeared.</p>

<p>The same problem appears outside banking.</p>

<p>An e-commerce application might create an order, reduce inventory, process a payment, generate an invoice, and send a confirmation email. If the payment succeeds but the order creation fails, the customer has paid for a product that the system doesn’t believe exists. Likewise, in a loan management system, a repayment may update the outstanding balance, post accounting entries, and generate a receipt. If only some of those updates complete, financial records quickly become unreliable.</p>

<p>These problems aren’t caused by bad algorithms or poor business logic. They’re caused by <strong>partial execution</strong> when only part of a larger operation succeeds.</p>

<p>This is precisely the problem database transactions were designed to solve.</p>

<p>A transaction ensures that multiple database operations behave as a single unit of work. Either every operation succeeds together, or every operation is rolled back as though nothing ever happened. There is no halfway point where your system is left in an inconsistent state.</p>

<p>For backend developers, understanding transactions is just as important as understanding APIs or databases themselves. They are the foundation upon which reliable financial systems, booking platforms, inventory systems, healthcare applications, and countless other business-critical systems are built.</p>

<hr />

<h1 id="what-is-a-database-transaction">What Is a Database Transaction?</h1>

<p>A database transaction is a collection of one or more database operations that the database treats as a single logical operation.</p>

<p>Instead of thinking about individual SQL statements, think about the business process they represent.</p>

<p>Suppose a customer purchases the last laptop in your online store. That single purchase might require your application to:</p>

<ul>
  <li>Create an order.</li>
  <li>Deduct one item from inventory.</li>
  <li>Reserve the shipment.</li>
  <li>Record the payment.</li>
  <li>Create an invoice.</li>
</ul>

<p>Although these are separate SQL statements, they represent one business event. Either all of them should succeed, or none of them should.</p>

<p>That’s exactly what a transaction guarantees.</p>

<p>Without transactions, every statement executes independently. If statement number four fails, the previous three remain committed, leaving your data inconsistent.</p>

<p>With transactions, the database waits until you’re satisfied that every operation has completed successfully. Only then are the changes permanently saved.</p>

<hr />

<h1 id="understanding-transactions-through-a-simple-example">Understanding Transactions Through a Simple Example</h1>

<p>Let’s return to the banking example.</p>

<p>Alice wants to transfer <strong>KES 10,000</strong> to Bob.</p>

<p>A simplified version of the SQL might look like this:</p>

<pre><code class="language-sql">UPDATE accounts
SET balance = balance - 10000
WHERE account_id = 1;

UPDATE accounts
SET balance = balance + 10000
WHERE account_id = 2;
</code></pre>

<p>At first glance, this seems perfectly reasonable.</p>

<p>But imagine the database crashes immediately after the first statement executes.</p>

<p>Alice’s balance has already been reduced.</p>

<p>Bob’s balance has not increased.</p>

<p>The system now contains incorrect financial data.</p>

<p>This is why production systems rarely execute related operations independently.</p>

<p>Instead, they wrap them inside a transaction.</p>

<pre><code class="language-sql">BEGIN;

UPDATE accounts
SET balance = balance - 10000
WHERE account_id = 1;

UPDATE accounts
SET balance = balance + 10000
WHERE account_id = 2;

COMMIT;
</code></pre>

<p>If every statement succeeds, the database executes the <code>COMMIT</code>, making the changes permanent.</p>

<p>If any statement fails before that point, the application issues a <code>ROLLBACK</code>, and the database restores itself to exactly the state it was in before the transaction began.</p>

<p>To the outside world, it appears as though the failed transfer never happened.</p>

<p>This “all-or-nothing” behavior is what makes transactions so valuable.</p>

<hr />

<h1 id="the-lifecycle-of-a-transaction">The Lifecycle of a Transaction</h1>

<p>Every transaction follows a predictable lifecycle.</p>

<pre><code class="language-text">BEGIN
   │
Execute SQL Operations
   │
Everything Successful?
   │
 ┌─ Yes ─────────────┐
 │                   │
COMMIT          Changes Saved
 │
 └─ No ──────────────┐
                     │
                 ROLLBACK
                     │
          Database Restored
</code></pre>

<p>The process begins with <code>BEGIN</code>, which tells the database to temporarily hold all modifications rather than immediately committing them.</p>

<p>The application then performs one or more operations. These could involve inserting records, updating balances, deleting data, or modifying relationships between tables.</p>

<p>If every operation succeeds, the application calls <code>COMMIT</code>. At that point, the database permanently saves all changes.</p>

<p>If anything goes wrong along the way, perhaps a validation error, a database constraint violation, or even an unexpected server failure, the transaction is rolled back, discarding every change made since <code>BEGIN</code>.</p>

<p>The beauty of this model is that the database itself guarantees consistency. Developers don’t need to manually undo every failed operation because the database handles that responsibility.</p>

<hr />

<h1 id="the-four-acid-properties">The Four ACID Properties</h1>

<p>When developers discuss transactions, you’ll almost always hear the term <strong>ACID</strong>.</p>

<p>Despite sounding intimidating, ACID simply describes the guarantees that modern relational databases provide when executing transactions.</p>

<h2 id="atomicity">Atomicity</h2>

<p>Atomicity means that a transaction is indivisible.</p>

<p>Either every operation succeeds, or none of them do.</p>

<p>Returning to our banking example, it makes no sense for money to be deducted from one account without being added to another. The transaction must succeed completely or fail completely.</p>

<p>Think of flipping a light switch.</p>

<p>The light cannot be half on.</p>

<p>Similarly, a transaction cannot be half completed.</p>

<hr />

<h2 id="consistency">Consistency</h2>

<p>Consistency ensures that every transaction leaves the database in a valid state.</p>

<p>Business rules should always remain true.</p>

<p>If your application enforces that inventory can never become negative, then a successful transaction should never violate that rule.</p>

<p>Likewise, if your accounting system requires every journal entry to balance, no committed transaction should ever leave the ledger unbalanced.</p>

<p>Consistency isn’t about preventing bugs in your application logic; it’s about ensuring that completed transactions respect the rules defined by your database and your business.</p>

<hr />

<h2 id="isolation">Isolation</h2>

<p>Isolation becomes important when multiple users interact with the system simultaneously.</p>

<p>Imagine two customers attempting to purchase the last available ticket for a concert.</p>

<p>Without proper isolation, both requests may read the inventory before either updates it. Both believe the ticket is available, and both complete the purchase.</p>

<p>You’ve now sold the same seat twice.</p>

<p>Isolation ensures that concurrent transactions don’t interfere with one another in ways that produce inconsistent results.</p>

<p>In our previous article, we discussed <strong>race conditions</strong> situations where multiple requests compete to modify the same data. Isolation is one of the database mechanisms used to prevent those concurrency problems.</p>

<p>We’ll explore isolation levels in greater depth in the next article because they deserve an entire discussion of their own.</p>

<hr />

<h2 id="durability">Durability</h2>

<p>Durability guarantees that once a transaction has been committed, the changes are permanent.</p>

<p>Even if the server loses power immediately after the commit, the database ensures that committed data survives.</p>

<p>Modern databases achieve this through techniques such as write-ahead logging, transaction logs, and crash recovery.</p>

<p>For developers, the important takeaway is simple: once the database confirms a successful commit, you can trust that the data has been safely stored.</p>

<hr />

<h1 id="transactions-solve-partial-failures-not-every-problem">Transactions Solve Partial Failures: Not Every Problem</h1>

<p>One misconception among newer developers is that transactions magically solve every data consistency problem.</p>

<p>They don’t.</p>

<p>Transactions protect against <strong>partial execution</strong>.</p>

<p>Suppose your application updates three tables and crashes after updating the second one. A transaction ensures that the database rolls everything back, preventing inconsistent data. However, transactions don’t automatically solve concurrency problems.</p>

<p>Imagine two users attempting to withdraw money from the same account simultaneously.
Each transaction independently checks the balance before either completes. If both see the same balance and both proceed, the final result may still be incorrect depending on your isolation level. This isn’t a transaction problem. It’s a concurrency problem.</p>

<p>That’s why understanding race conditions and transactions together is so important. They solve different classes of reliability issues.</p>

<hr />

<h1 id="common-places-youll-use-transactions">Common Places You’ll Use Transactions</h1>

<p>Transactions appear almost everywhere in modern backend systems.</p>

<p>Payment processing is perhaps the most obvious example. Charging a customer’s card, recording the payment, updating invoices, and generating accounting entries should either all succeed or all fail together.</p>

<p>Inventory management systems use transactions to ensure stock counts remain accurate even when multiple customers are purchasing products simultaneously.</p>

<p>Booking platforms rely on transactions to reserve hotel rooms, airline seats, or event tickets without creating conflicting reservations.</p>

<p>Loan management systems use transactions when posting repayments, updating outstanding balances, calculating accrued interest, and recording accounting entries.</p>

<p>Healthcare systems use them to ensure patient records, prescriptions, billing information, and appointment schedules remain synchronized.</p>

<p>Any time a business operation spans multiple database changes, a transaction is usually involved.</p>

<hr />

<h1 id="common-mistakes-developers-make">Common Mistakes Developers Make</h1>

<p>One of the most common mistakes is keeping transactions open for too long.</p>

<p>Imagine starting a transaction, calling a third-party payment API, waiting several seconds for a response, and only then committing the transaction.</p>

<p>During that entire period, database resources may remain locked, reducing performance for other users.</p>

<p>A better approach is to perform external API calls before starting the transaction whenever possible, keeping the transaction focused solely on database operations.</p>

<p>Another common mistake is assuming transactions automatically protect against concurrent updates. As we’ve already seen, concurrency introduces an entirely different set of challenges that require locking strategies or appropriate isolation levels.</p>

<p>Finally, developers sometimes forget that transactions should represent business operations not individual SQL statements. Wrapping every single query in its own transaction rarely provides meaningful benefits.</p>

<hr />

<h1 id="transactions-in-modern-frameworks">Transactions in Modern Frameworks</h1>

<p>Fortunately, most frameworks make transactions straightforward to use.</p>

<p>Laravel offers the <code>DB::transaction()</code> helper.</p>

<p>Django provides <code>transaction.atomic()</code>.</p>

<p>Entity Framework supports <code>BeginTransactionAsync()</code>.</p>

<p>Spring Boot uses the <code>@Transactional</code> annotation.</p>

<p>Although the syntax differs, the underlying principle never changes. The framework simply tells the database when to begin the transaction, when to commit it, and when to roll it back if something goes wrong.</p>

<p>Understanding the concept matters far more than memorizing framework-specific syntax.</p>

<hr />

<h1 id="transactions-idempotency-and-race-conditions">Transactions, Idempotency, and Race Conditions</h1>

<p>If you’ve been following this series, you may have noticed that each concept addresses a different reliability challenge.</p>

<p><strong>Idempotency</strong> protects against duplicate requests by ensuring that repeating the same request doesn’t produce duplicate side effects.</p>

<p><strong>Race conditions</strong> occur when multiple requests compete to modify shared data simultaneously, leading to unpredictable outcomes.</p>

<p><strong>Transactions</strong> ensure that a group of related database operations either all succeed together or all fail together.</p>

<p>Reliable backend systems typically rely on all three.</p>

<p>Imagine a payment API.</p>

<p>Idempotency prevents customers from being charged twice if they retry a request.</p>

<p>Transactions ensure that charging the customer, recording the payment, and updating account balances either all succeed or all fail.</p>

<p>Proper concurrency control ensures that two simultaneous payment requests don’t corrupt shared data.</p>

<p>Each concept complements the others rather than replacing them.</p>

<hr />

<h1 id="final-thoughts">Final Thoughts</h1>

<p>Transactions are one of the reasons relational databases remain so powerful. They provide developers with a reliable mechanism for preserving data integrity even when failures occur.</p>

<p>As systems become larger and more distributed, failures become inevitable. Servers crash, networks fail, APIs time out, and users submit requests simultaneously. Transactions don’t eliminate those realities, but they ensure your database remains consistent when they happen.</p>

<p>Whenever you’re implementing a feature that modifies multiple pieces of related data, pause for a moment and ask yourself:</p>

<blockquote>
  <p><strong>What happens if this operation fails halfway through?</strong></p>
</blockquote>

<p>If the answer is “my system ends up in an inconsistent state,” then you’ve almost certainly found a place where a database transaction belongs.</p>

<p>In the next article, we’ll build on this foundation by exploring <strong>database isolation levels</strong> and why two perfectly valid transactions can still interfere with one another when they run at the same time.</p>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/database-transactions-explained.jpg" medium="image" />
  
  
    
      <category>Database</category>
    
      <category>Software Engineering</category>
    
  
  
    
      <category>Database</category>
    
      <category>Software Engineering</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Race Conditions Explained: The Concurrency Bug Every Backend Developer Should Understand</title>
      <link>https://billyokeyo.dev/posts/race-conditions-explained/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/race-conditions-explained/</guid>
      <pubDate>Sun, 28 Jun 2026 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-06-30T15:43:26+00:00</atom:updated>
  
      <description><![CDATA[Race conditions are among the hardest bugs to reproduce because they don't happen every time. They may only appear under heavy traffic, high concurrency, or perfect timing. Everything works during testing—until your application reaches production. This article explains what race conditions are, why they happen, how they relate to idempotency, and the techniques developers use to prevent them.]]></description>
      <content:encoded><![CDATA[<p>Imagine you’re trying to buy the last ticket for your favorite concert.</p>

<p>The website shows:</p>

<blockquote>
  <p><strong>Only 1 ticket remaining.</strong></p>
</blockquote>

<p>At exactly the same moment, someone else clicks <strong>Buy</strong>.</p>

<p>Both of you complete payment.</p>

<p>Both of you receive confirmation emails.</p>

<p>But there was only one ticket.</p>

<p>How did two people successfully purchase the same seat?</p>

<p>Now imagine the same thing happening in a banking application.</p>

<p>Two ATM withdrawals happen at almost the same time.</p>

<p>Both check the balance before either transaction finishes.</p>

<p>Both think there’s enough money.</p>

<p>Both approve the withdrawal.</p>

<p>The account ends up with a negative balance.</p>

<p>Neither application is necessarily “broken.”</p>

<p>Instead, they suffer from one of the most common problems in software engineering:</p>

<p><strong>Race conditions.</strong></p>

<p>Race conditions are among the hardest bugs to reproduce because they don’t happen every time. They may only appear under heavy traffic, high concurrency, or perfect timing. Everything works during testing until your application reaches production.</p>

<p>In this article, we’ll explore what race conditions are, why they happen, how they relate to idempotency, and the techniques developers use to prevent them.</p>

<hr />

<h1 id="what-is-a-race-condition">What Is a Race Condition?</h1>

<p>A race condition occurs when <strong>two or more operations access and modify the same piece of data at the same time, and the final result depends on the order in which they execute.</strong></p>

<p>The important phrase is:</p>

<blockquote>
  <p><strong>The outcome depends on timing.</strong></p>
</blockquote>

<p>That’s what makes race conditions so dangerous.</p>

<p>Sometimes everything works.</p>

<p>Sometimes everything breaks.</p>

<p>The exact same code can produce different results simply because two requests happened a few milliseconds apart.</p>

<hr />

<h1 id="a-simple-analogy">A Simple Analogy</h1>

<p>Imagine two people standing in front of a cookie jar.</p>

<p>The jar contains exactly one cookie.</p>

<p>Both people look inside.</p>

<p>Both see one cookie.</p>

<p>Both reach in.</p>

<p>Both believe they’ll get the cookie.</p>

<p>Reality says otherwise.</p>

<p>Only one cookie exists.</p>

<p>The mistake happened because both people <strong>checked the state before either updated it.</strong></p>

<p>Software behaves exactly the same way.</p>

<hr />

<h1 id="a-real-banking-example">A Real Banking Example</h1>

<p>Suppose an account has:</p>

<pre><code class="language-text">Balance = $100
</code></pre>

<p>Two withdrawal requests arrive simultaneously.</p>

<pre><code>Request A → Withdraw $80

Request B → Withdraw $50
</code></pre>

<p>Without synchronization:</p>

<pre><code>Request A
↓

Read Balance ($100)

------------

Request B

↓

Read Balance ($100)

------------

Request A

Balance = $20

------------

Request B

Balance = $50
</code></pre>

<p>Depending on timing, the final balance might be:</p>

<pre><code>$20

or

$50

or

-$30
</code></pre>

<p>None of these outcomes are guaranteed.</p>

<p>This is a race condition.</p>

<hr />

<h1 id="why-it-works-during-development">Why It Works During Development</h1>

<p>Most developers test applications alone.</p>

<p>One request.</p>

<p>One browser.</p>

<p>One user.</p>

<p>Everything works perfectly.</p>

<p>Production is different.</p>

<p>Imagine:</p>

<ul>
  <li>5,000 users</li>
  <li>Hundreds of requests per second</li>
  <li>Multiple application servers</li>
  <li>Database replication</li>
  <li>Network latency</li>
</ul>

<p>The probability of two requests colliding becomes much higher.</p>

<p>Race conditions often appear only after an application becomes successful.</p>

<p>Ironically, scaling your application can reveal bugs that never existed during development.</p>

<hr />

<h1 id="race-conditions-vs-idempotency">Race Conditions vs Idempotency</h1>

<p>If you’ve read my previous article on idempotency, if not, read it first <a href="https://billyokeyo.dev/posts/idempotency-explained/">here</a>. You might wonder whether they’re the same thing.</p>

<p>They’re related, but they solve different problems.</p>

<h3 id="idempotency-answers">Idempotency answers:</h3>

<blockquote>
  <p>What if the <strong>same request</strong> is sent twice?</p>
</blockquote>

<p>Example:</p>

<pre><code>POST /payments

↓

Retry

↓

POST /payments
</code></pre>

<p>The solution is an <strong>Idempotency-Key</strong>.</p>

<p>The same request produces the same result.</p>

<hr />

<h3 id="race-conditions-answer">Race conditions answer:</h3>

<blockquote>
  <p>What if <strong>different requests</strong> happen at the same time?</p>
</blockquote>

<p>Example:</p>

<pre><code>User A buys last ticket

↓

User B buys last ticket
</code></pre>

<p>These are two legitimate requests from different users.</p>

<p>An idempotency key won’t help because the requests are not duplicates.</p>

<p>Race conditions require synchronization, not deduplication.</p>

<hr />

<h1 id="real-world-examples">Real-World Examples</h1>

<h2 id="airline-seat-booking">Airline Seat Booking</h2>

<p>Only one seat remains.</p>

<p>Two customers purchase it simultaneously.</p>

<p>Without proper locking:</p>

<ul>
  <li>Seat sold twice</li>
  <li>Refund required</li>
  <li>Customer frustration</li>
</ul>

<hr />

<h2 id="e-commerce-inventory">E-Commerce Inventory</h2>

<p>Stock:</p>

<pre><code>Laptop

Quantity = 1
</code></pre>

<p>Two customers purchase simultaneously.</p>

<p>Without protection:</p>

<p>Inventory becomes:</p>

<pre><code>-1
</code></pre>

<p>Now you’ve sold a product you don’t have.</p>

<hr />

<h2 id="loan-approval-systems">Loan Approval Systems</h2>

<p>Imagine two loan officers reviewing the same application.</p>

<p>Officer A:</p>

<p>Approve.</p>

<p>Officer B:</p>

<p>Reject.</p>

<p>If both updates happen simultaneously without coordination, the final loan status depends entirely on timing.</p>

<hr />

<h2 id="coupon-redemption">Coupon Redemption</h2>

<p>Promotion:</p>

<pre><code>First 100 customers only
</code></pre>

<p>If multiple requests update the redemption count simultaneously, you might accidentally issue:</p>

<pre><code>103

105

110

coupons.
</code></pre>

<hr />

<h1 id="how-race-conditions-happen">How Race Conditions Happen</h1>

<p>Most race conditions follow this pattern:</p>

<pre><code>Read

↓

Modify

↓

Write
</code></pre>

<p>The danger lies between <strong>Read</strong> and <strong>Write</strong>.</p>

<p>Example:</p>

<pre><code>Read Balance

↓

Calculate New Balance

↓

Update Balance
</code></pre>

<p>If another request changes the balance between those steps, your calculation becomes outdated.</p>

<p>This is known as a <strong>Lost Update</strong> problem.</p>

<hr />

<h1 id="solution-1-database-transactions">Solution 1: Database Transactions</h1>

<p>Transactions ensure multiple operations succeed or fail together.</p>

<p>Example:</p>

<pre><code class="language-sql">BEGIN;

SELECT balance
FROM accounts
WHERE id = 1;

UPDATE accounts
SET balance = balance - 80
WHERE id = 1;

COMMIT;
</code></pre>

<p>Transactions protect data consistency.</p>

<p>However, they don’t automatically eliminate every race condition.</p>

<p>Isolation levels matter too.</p>

<hr />

<h1 id="solution-2-row-level-locks">Solution 2: Row-Level Locks</h1>

<p>Many relational databases allow locking a row while it’s being updated.</p>

<p>Example:</p>

<pre><code class="language-sql">SELECT *
FROM accounts
WHERE id = 1
FOR UPDATE;
</code></pre>

<p>Now:</p>

<p>Request A locks the row.</p>

<p>Request B must wait.</p>

<p>Only after Request A finishes can Request B continue.</p>

<p>This guarantees consistent updates.</p>

<hr />

<h1 id="solution-3-optimistic-locking">Solution 3: Optimistic Locking</h1>

<p>Instead of preventing conflicts, optimistic locking detects them.</p>

<p>Imagine a version number.</p>

<pre><code>Account

Balance = 100

Version = 5
</code></pre>

<p>Update:</p>

<pre><code>WHERE Version = 5
</code></pre>

<p>If another request updates the row first:</p>

<pre><code>Version = 6
</code></pre>

<p>Your update fails.</p>

<p>The client retries with fresh data.</p>

<p>Optimistic locking works well when collisions are relatively rare.</p>

<hr />

<h1 id="solution-4-distributed-locks">Solution 4: Distributed Locks</h1>

<p>What if your application runs on multiple servers?</p>

<pre><code>Server A

↓

Database

↑

Server B
</code></pre>

<p>A normal in-memory lock won’t work because each server has its own memory.</p>

<p>Instead, developers use distributed locking systems like:</p>

<ul>
  <li>Redis (Redlock)</li>
  <li>ZooKeeper</li>
  <li>etcd</li>
  <li>Consul</li>
</ul>

<p>These coordinate access across multiple application instances.</p>

<hr />

<h1 id="solution-5-atomic-database-operations">Solution 5: Atomic Database Operations</h1>

<p>Sometimes you don’t need to:</p>

<pre><code>Read

↓

Calculate

↓

Write
</code></pre>

<p>Instead, let the database perform the update atomically.</p>

<p>Bad:</p>

<pre><code class="language-sql">SELECT quantity;

quantity--;

UPDATE products;
</code></pre>

<p>Better:</p>

<pre><code class="language-sql">UPDATE products

SET quantity = quantity - 1

WHERE quantity &gt; 0;
</code></pre>

<p>Now the database guarantees consistency.</p>

<hr />

<h1 id="detecting-race-conditions">Detecting Race Conditions</h1>

<p>One reason race conditions are difficult is that they rarely appear during manual testing.</p>

<p>Ways to uncover them include:</p>

<ul>
  <li>Load testing with concurrent users</li>
  <li>Stress testing</li>
  <li>Running parallel integration tests</li>
  <li>Simulating delayed responses</li>
  <li>Chaos engineering</li>
  <li>Monitoring production logs</li>
</ul>

<p>If a bug only appears “sometimes,” concurrency should be one of your first suspects.</p>

<hr />

<h1 id="best-practices">Best Practices</h1>

<p>When designing systems that handle shared data:</p>

<ul>
  <li>Keep transactions short.</li>
  <li>Avoid long-running locks.</li>
  <li>Prefer atomic database operations where possible.</li>
  <li>Use optimistic locking when contention is low.</li>
  <li>Use pessimistic locking for critical resources.</li>
  <li>Test with concurrent requests, not just sequential ones.</li>
  <li>Understand your database’s transaction isolation levels.</li>
</ul>

<p>Most importantly, always ask:</p>

<blockquote>
  <p><strong>“What happens if two users do this at exactly the same time?”</strong></p>
</blockquote>

<hr />

<h1 id="final-thoughts">Final Thoughts</h1>

<p>Race conditions aren’t caused by bad developers, they’re caused by systems becoming concurrent.</p>

<p>As applications grow, users interact simultaneously, background jobs overlap, and services communicate in parallel. Timing becomes unpredictable.</p>

<p>That’s why building reliable software isn’t just about writing correct logic for one request. It’s about ensuring your logic remains correct when hundreds or thousands of requests happen together.</p>

<p>If idempotency protects you from duplicate requests, race condition handling protects you from competing requests.</p>

<p>Together, they form two of the most important building blocks for designing resilient APIs and distributed systems.</p>

<p>The next time you write code that reads, modifies, and writes shared data, pause for a moment and ask:</p>

<blockquote>
  <p><strong>“What happens if someone else does this at the exact same time?”</strong></p>
</blockquote>

<p>If you don’t know the answer, you’ve just found your next engineering problem to solve.</p>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/race-conditions-explained.jpg" medium="image" />
  
  
    
      <category>API Design</category>
    
      <category>Software Testing</category>
    
  
  
    
      <category>Race Conditions</category>
    
      <category>API Design</category>
    
      <category>Software Testing</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Idempotency Explained: Building APIs That Survive Retries</title>
      <link>https://billyokeyo.dev/posts/idempotency-explained/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/idempotency-explained/</guid>
      <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-06-30T15:43:26+00:00</atom:updated>
  
      <description><![CDATA[Idempotency is a critical concept in API design that ensures that an operation can be performed multiple times without changing the final result beyond the first successful execution. This article explains what idempotency is, why it matters, and how to implement it in your own APIs.]]></description>
      <content:encoded><![CDATA[<p>Imagine you’re purchasing a product online.</p>

<p>You click the <strong>“Pay Now”</strong> button.</p>

<p>Nothing happens.</p>

<p>After a few seconds, you assume the request failed, so you click the button again.</p>

<p>And again.</p>

<p>A few minutes later, you discover you’ve been charged three times.</p>

<p>What happened?</p>

<p>From the user’s perspective, the payment seemed to fail. From the server’s perspective, however, it successfully processed every request it received.</p>

<p>This is one of the most common problems in distributed systems, and it’s exactly why idempotency exists.</p>

<p>Whether you’re building payment systems, booking platforms, inventory management software, or any API that changes data, retries are inevitable. Networks fail, clients time out, mobile connections drop, and users double-click buttons.</p>

<p>A well-designed API should survive these retries without creating duplicate side effects.</p>

<p>In this article, we’ll explore what idempotency is, why it matters, and how to implement it in your own APIs.</p>

<hr />

<h1 id="what-is-idempotency">What Is Idempotency?</h1>

<p>In simple terms, <strong>an idempotent operation can be performed multiple times without changing the final result beyond the first successful execution.</strong></p>

<p>For example:</p>

<pre><code>Turn on the light.
Turn on the light again.
Turn on the light again.
</code></pre>

<p>The light is still <strong>ON</strong>.</p>

<p>Nothing new happened after the first request.</p>

<p>The final state remains the same.</p>

<p>That’s idempotency.</p>

<p>Now compare it with this:</p>

<pre><code>Deposit $100
Deposit $100
Deposit $100
</code></pre>

<p>Your account balance increases by <strong>$300</strong>.</p>

<p>This operation is <strong>not idempotent</strong> because every request changes the system.</p>

<hr />

<h1 id="why-retries-happen">Why Retries Happen</h1>

<p>Many developers assume users only send one request.</p>

<p>Reality is different.</p>

<p>Requests are retried because of:</p>

<ul>
  <li>Slow internet connections</li>
  <li>Gateway timeouts</li>
  <li>Reverse proxies</li>
  <li>Mobile network interruptions</li>
  <li>Browser refreshes</li>
  <li>Double-clicking buttons</li>
  <li>Client retry mechanisms</li>
  <li>Load balancers</li>
  <li>Microservice communication failures</li>
</ul>

<p>Imagine this timeline:</p>

<pre><code>Client -------- POST /payments --------&gt; API

             Payment succeeds

API -------- 200 OK --------X

(Response never reaches client)

Client waits...

Client retries.

POST /payments again.
</code></pre>

<p>The client believes the payment failed.</p>

<p>The server already completed it.</p>

<p>Without idempotency…</p>

<p>The payment happens twice.</p>

<hr />

<h1 id="http-methods-and-idempotency">HTTP Methods and Idempotency</h1>

<p>HTTP itself distinguishes between idempotent and non-idempotent methods.</p>

<h3 id="get">GET</h3>

<pre><code>GET /users/10
</code></pre>

<p>Read the user.</p>

<p>Call it once.</p>

<p>Call it 100 times.</p>

<p>Nothing changes.</p>

<p>Idempotent</p>

<hr />

<h3 id="put">PUT</h3>

<pre><code>PUT /users/10
{
   "name": "Billy"
}
</code></pre>

<p>Replacing the same resource repeatedly produces the same result.</p>

<p>Idempotent</p>

<hr />

<h3 id="delete">DELETE</h3>

<pre><code>DELETE /users/10
</code></pre>

<p>Delete the user.</p>

<p>Deleting an already deleted user doesn’t delete them twice.</p>

<p>The final state is still:</p>

<pre><code>User does not exist.
</code></pre>

<p>Idempotent</p>

<hr />

<h3 id="post">POST</h3>

<pre><code>POST /orders
</code></pre>

<p>Create a new order.</p>

<p>Call it twice.</p>

<p>You now have two orders.</p>

<p>Not idempotent</p>

<p>This is why POST requests often require additional protection.</p>

<hr />

<h1 id="why-payment-apis-use-idempotency-keys">Why Payment APIs Use Idempotency Keys</h1>

<p>Payment providers like Stripe popularized the use of <strong>Idempotency Keys</strong>.</p>

<p>The idea is simple.</p>

<p>The client generates a unique identifier.</p>

<p>Example:</p>

<pre><code>Idempotency-Key:
6ab89d3b-acde-4d71-b20d-483d8d0ef091
</code></pre>

<p>Every retry sends the same key.</p>

<pre><code>POST /payments

Idempotency-Key:
6ab89d3b-acde-4d71-b20d-483d8d0ef091
</code></pre>

<p>When the server receives the request:</p>

<ol>
  <li>Check if this key already exists.</li>
  <li>If not, process the payment.</li>
  <li>Save both the key and the response.</li>
  <li>Return the response.</li>
</ol>

<p>If the same request arrives again with the same key:</p>

<p>Instead of charging the customer again…</p>

<p>Return the previously stored response.</p>

<pre><code>Client

POST /payments
Key: ABC123

↓

Server

Charge customer

↓

Store

ABC123 → Payment #456

↓

Return success

---

Retry

POST /payments
Key: ABC123

↓

Lookup

ABC123 exists

↓

Return Payment #456

No second charge.
</code></pre>

<hr />

<h1 id="implementing-idempotency">Implementing Idempotency</h1>

<p>A common workflow looks like this.</p>

<h2 id="step-1">Step 1</h2>

<p>Receive request.</p>

<pre><code>POST /orders
</code></pre>

<p>Headers</p>

<pre><code>Idempotency-Key:
XYZ987
</code></pre>

<hr />

<h2 id="step-2">Step 2</h2>

<p>Search database.</p>

<pre><code>SELECT *
FROM idempotency_keys
WHERE key = 'XYZ987'
</code></pre>

<p>Found?</p>

<p>Yes.</p>

<p>Return stored response.</p>

<p>Done.</p>

<hr />

<h2 id="step-3">Step 3</h2>

<p>Not found?</p>

<p>Create the resource.</p>

<pre><code>Create Order
</code></pre>

<hr />

<h2 id="step-4">Step 4</h2>

<p>Store:</p>

<pre><code>Key

Response

Status Code

Timestamp
</code></pre>

<p>Now every retry returns the same response.</p>

<hr />

<h1 id="example-in-nodejs-express">Example in Node.js (Express)</h1>

<pre><code class="language-javascript">app.post("/payments", async (req, res) =&gt; {
    const key = req.header("Idempotency-Key");

    const existing = await Idempotency.findOne({ key });

    if (existing) {
        return res.status(existing.status).json(existing.response);
    }

    const payment = await processPayment(req.body);

    await Idempotency.create({
        key,
        status: 201,
        response: payment,
    });

    return res.status(201).json(payment);
});
</code></pre>

<h2 id="python-fastapi">Python (FastAPI)</h2>

<pre><code class="language-python">from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.post("/payments")
async def create_payment(request: Request):
    key = request.headers.get("Idempotency-Key")

    existing = await Idempotency.find_one(key=key)

    if existing:
        return JSONResponse(
            content=existing.response,
            status_code=existing.status,
        )

    body = await request.json()
    payment = await process_payment(body)

    await Idempotency.create(
        key=key,
        status=201,
        response=payment,
    )

    return JSONResponse(content=payment, status_code=201)
</code></pre>

<h2 id="c-aspnet-core">C# (ASP.NET Core)</h2>

<pre><code class="language-csharp">app.MapPost("/payments", async (
    HttpRequest request,
    IdempotencyStore store,
    PaymentService payments) =&gt;
{
    var key = request.Headers["Idempotency-Key"].ToString();

    var existing = await store.FindAsync(key);
    if (existing is not null)
    {
        return Results.Json(existing.Response, statusCode: existing.Status);
    }

    var body = await request.ReadFromJsonAsync&lt;PaymentRequest&gt;();
    var payment = await payments.ProcessAsync(body!);

    await store.CreateAsync(new IdempotencyRecord(key, 201, payment));

    return Results.Json(payment, statusCode: StatusCodes.Status201Created);
});
</code></pre>

<h2 id="go">Go</h2>

<pre><code class="language-go">func createPayment(w http.ResponseWriter, r *http.Request) {
    key := r.Header.Get("Idempotency-Key")

    existing, err := idempotency.FindOne(r.Context(), key)
    if err == nil &amp;&amp; existing != nil {
        w.WriteHeader(existing.Status)
        json.NewEncoder(w).Encode(existing.Response)
        return
    }

    var body PaymentRequest
    if err := json.NewDecoder(r.Body).Decode(&amp;body); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    payment, err := processPayment(r.Context(), body)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    if err := idempotency.Create(r.Context(), IdempotencyRecord{
        Key:      key,
        Status:   http.StatusCreated,
        Response: payment,
    }); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(payment)
}
</code></pre>

<h2 id="laravel">Laravel</h2>

<pre><code class="language-php">Route::post('/payments', function (Request $request) {
    $key = $request-&gt;header('Idempotency-Key');

    $existing = Idempotency::where('key', $key)-&gt;first();

    if ($existing) {
        return response()-&gt;json($existing-&gt;response, $existing-&gt;status);
    }

    $payment = processPayment($request-&gt;all());

    Idempotency::create([
        'key' =&gt; $key,
        'status' =&gt; 201,
        'response' =&gt; $payment,
    ]);

    return response()-&gt;json($payment, 201);
});
</code></pre>

<p>The logic is surprisingly simple.</p>

<p>The complexity comes from storing and managing the keys correctly.</p>

<hr />

<h1 id="where-should-idempotency-keys-be-stored">Where Should Idempotency Keys Be Stored?</h1>

<p>Options include:</p>

<h2 id="database">Database</h2>

<p>Best for most applications.</p>

<p>Pros:</p>

<ul>
  <li>Persistent</li>
  <li>Reliable</li>
  <li>Easy to query</li>
</ul>

<p>Cons:</p>

<ul>
  <li>Slightly slower</li>
</ul>

<hr />

<h2 id="redis">Redis</h2>

<p>Excellent for high-volume APIs.</p>

<p>Pros:</p>

<ul>
  <li>Extremely fast</li>
  <li>TTL support</li>
  <li>Easy expiration</li>
</ul>

<p>Many APIs automatically expire keys after 24 hours.</p>

<hr />

<h2 id="in-memory">In-Memory</h2>

<p>Useful only during development.</p>

<p>Not recommended for production.</p>

<p>Restarting the server loses everything.</p>

<hr />

<h1 id="common-mistakes">Common Mistakes</h1>

<h2 id="reusing-keys">Reusing Keys</h2>

<p>Every logical operation should have its own unique key.</p>

<p>Bad:</p>

<pre><code>ABC123

used today

used tomorrow
</code></pre>

<p>Good:</p>

<pre><code>New checkout

↓

Generate new UUID
</code></pre>

<hr />

<h2 id="ignoring-request-differences">Ignoring Request Differences</h2>

<p>Suppose the first request is:</p>

<pre><code>$50
</code></pre>

<p>The retry is:</p>

<pre><code>$500
</code></pre>

<p>Same key.</p>

<p>Different body.</p>

<p>The server should reject this request because the key is being reused for a different operation.</p>

<hr />

<h2 id="never-expiring-keys">Never Expiring Keys</h2>

<p>Keeping millions of old keys forever wastes storage.</p>

<p>Most APIs expire them after:</p>

<ul>
  <li>24 hours</li>
  <li>48 hours</li>
  <li>7 days</li>
</ul>

<p>depending on business requirements.</p>

<hr />

<h1 id="real-world-use-cases">Real-World Use Cases</h1>

<p>Idempotency is valuable anywhere duplicate requests could have costly consequences.</p>

<p>Examples include:</p>

<ul>
  <li>Payment processing</li>
  <li>Bank transfers</li>
  <li>Order creation</li>
  <li>Hotel reservations</li>
  <li>Flight bookings</li>
  <li>Ticket purchases</li>
  <li>Subscription billing</li>
  <li>Inventory updates</li>
  <li>Webhook processing</li>
  <li>Email sending</li>
  <li>Message queues</li>
</ul>

<p>If performing the same action twice could create an incorrect outcome, idempotency is worth considering.</p>

<hr />

<h1 id="when-you-dont-need-idempotency">When You Don’t Need Idempotency</h1>

<p>Not every endpoint needs an idempotency key.</p>

<p>For example:</p>

<pre><code>GET /posts
</code></pre>

<p>No state changes.</p>

<p>No duplicates.</p>

<p>No problem.</p>

<p>Likewise, endpoints such as:</p>

<ul>
  <li>Search</li>
  <li>Filtering</li>
  <li>Reading reports</li>
  <li>Viewing profiles</li>
</ul>

<p>are already naturally idempotent.</p>

<p>Reserve idempotency mechanisms for operations where retries could create unintended side effects.</p>

<hr />

<h1 id="final-thoughts">Final Thoughts</h1>

<p>Idempotency isn’t just an implementation detail—it’s a reliability feature.</p>

<p>In distributed systems, retries are normal. Networks are unreliable, users click buttons more than once, and clients retry requests automatically. Instead of hoping those situations never happen, design your APIs to handle them gracefully.</p>

<p>By using idempotency keys, storing responses, validating retries, and choosing the right storage strategy, you can prevent duplicate orders, repeated payments, and other costly errors.</p>

<p>A resilient API isn’t one that never receives duplicate requests.</p>

<p>It’s one that produces the correct outcome even when duplicate requests inevitably arrive.</p>

<p>The next time you design a <code>POST</code> endpoint, ask yourself:</p>

<blockquote>
  <p><strong>“What happens if this request is sent twice?”</strong></p>
</blockquote>

<p>If the answer is “something bad,” it’s probably time to add idempotency.</p>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/idempotency-explained.jpg" medium="image" />
  
  
    
      <category>API Design</category>
    
      <category>Software Testing</category>
    
  
  
    
      <category>Idempotency</category>
    
      <category>API Design</category>
    
      <category>Software Testing</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Lessons Learned from Writing My First 72 Blog Posts</title>
      <link>https://billyokeyo.dev/posts/lessons-learned-fron-writing/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/lessons-learned-fron-writing/</guid>
      <pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-06-17T00:00:00+00:00</atom:updated>
  
      <description><![CDATA[Writing articles has made me a better developer. This post breaks down the mindset and process of effective writing, with practical steps and real examples to help you become a better problem solver. I wrote my first blog post in 2023 and have written 72 blog posts since then.]]></description>
      <content:encoded><![CDATA[<p>When I published my first blog post, I wasn’t thinking about reaching 72 articles.</p>

<p>I wasn’t thinking about traffic, search engine rankings, personal branding, or becoming a better writer. I simply wanted a place to share my thoughts, document what I was learning, and build something that belonged to me.</p>

<p>Like many developers, I started blogging because I was learning new things every day and wanted to keep track of my progress. At the time, I had no idea how much writing would teach me—not just about technology, but about consistency, communication, patience, and personal growth.</p>

<p>Now, after publishing my first 72 blog posts, I’ve learned lessons that extend far beyond blogging itself. Some lessons came from successes, while others came from mistakes and moments of frustration.</p>

<p>In this article, I want to share the most important lessons I’ve learned from publishing my first 72 blog posts.</p>

<h2 id="1-consistency-beats-perfection">1. Consistency Beats Perfection</h2>

<p>One of the biggest mistakes I made early on was trying to make every article perfect.</p>

<p>I would spend hours tweaking sentences, rewriting paragraphs, and overthinking every detail. Sometimes I delayed publishing because I felt an article wasn’t “good enough.”</p>

<p>Over time, I realized that perfection is the enemy of progress.</p>

<p>The articles that helped me grow weren’t necessarily my best articles. They were simply the articles I published.</p>

<p>Consistency creates momentum.</p>

<p>Publishing one article every week for a year is far more valuable than spending months trying to create a single masterpiece.</p>

<p>The biggest breakthrough came when I stopped chasing perfection and started focusing on showing up consistently.</p>

<h2 id="2-writing-clarifies-thinking">2. Writing Clarifies Thinking</h2>

<p>Many times, I thought I understood a topic until I tried to explain it in writing.</p>

<p>That’s when the gaps in my knowledge became obvious.</p>

<p>Writing forced me to organize my thoughts, simplify complex ideas, and identify areas where my understanding was incomplete.</p>

<p>The process taught me that learning and explaining are not the same thing.</p>

<p>If I couldn’t explain a concept clearly, I probably didn’t understand it as well as I thought.</p>

<p>This lesson made me a better learner and a better developer.</p>

<h2 id="3-nobody-reads-your-first-postsand-thats-okay">3. Nobody Reads Your First Posts—And That’s Okay</h2>

<p>One of the hardest realities for new bloggers is that almost nobody reads your early content.</p>

<p>I remember publishing articles and checking analytics repeatedly, hoping to see visitors.</p>

<p>Most of the time, there were very few.</p>

<p>At first, that felt discouraging.</p>

<p>But eventually I realized something important:</p>

<p>The purpose of your first blog posts isn’t to attract thousands of readers.</p>

<p>The purpose is to learn how to write.</p>

<p>Your first posts are practice.</p>

<p>Every article improves your skills, your confidence, and your ability to communicate.</p>

<p>The audience comes later.</p>

<h2 id="4-the-habit-matters-more-than-motivation">4. The Habit Matters More Than Motivation</h2>

<p>Motivation is unreliable.</p>

<p>Some days you’ll feel inspired.</p>

<p>Other days you’ll have no desire to write.</p>

<p>If blogging depends entirely on motivation, consistency becomes impossible.</p>

<p>One of the most valuable lessons I learned is that habits outperform motivation.</p>

<p>The writers who succeed aren’t necessarily the most talented.</p>

<p>They’re the ones who continue writing even when they don’t feel like it.</p>

<p>Creating a writing habit transformed blogging from an occasional activity into a regular part of my routine.</p>

<h2 id="5-every-post-doesnt-need-to-be-revolutionary">5. Every Post Doesn’t Need to Be Revolutionary</h2>

<p>Early in my blogging journey, I believed every article needed a unique insight or groundbreaking idea.</p>

<p>I was wrong.</p>

<p>Many of my most useful posts covered topics that had already been discussed countless times.</p>

<p>The difference was that I shared my own perspective and experience.</p>

<p>Your value doesn’t come from inventing entirely new ideas.</p>

<p>It comes from explaining ideas through your own lens.</p>

<p>There’s always someone who needs the explanation that only you can provide.</p>

<h2 id="6-writing-builds-confidence">6. Writing Builds Confidence</h2>

<p>Publishing online can feel intimidating.</p>

<p>You’re sharing your thoughts with strangers.</p>

<p>You’re exposing your ideas to criticism.</p>

<p>You’re putting your work in public.</p>

<p>At first, this can be uncomfortable.</p>

<p>But over time, each article builds confidence.</p>

<p>You become more comfortable expressing your opinions.</p>

<p>You stop worrying about being perfect.</p>

<p>You gain confidence in your ability to communicate and contribute valuable ideas.</p>

<p>That confidence eventually extends beyond blogging into other areas of life and work.</p>

<h2 id="7-blogging-is-a-long-term-game">7. Blogging Is a Long-Term Game</h2>

<p>One of the most important lessons I learned is that blogging rewards patience.</p>

<p>Results rarely happen overnight.</p>

<p>Traffic grows slowly.</p>

<p>Authority builds gradually.</p>

<p>Opportunities emerge unexpectedly.</p>

<p>Many people quit because they expect immediate results.</p>

<p>The reality is that blogging is similar to investing.</p>

<p>Small contributions made consistently over time eventually compound into significant outcomes.</p>

<p>The key is staying committed long enough to experience those benefits.</p>

<h2 id="8-publishing-teaches-more-than-consuming">8. Publishing Teaches More Than Consuming</h2>

<p>Before I started blogging, I spent most of my time consuming content.</p>

<p>I watched tutorials.</p>

<p>I read articles.</p>

<p>I followed courses.</p>

<p>While these activities were helpful, publishing taught me far more.</p>

<p>Creating content forces you to think deeply, research thoroughly, and communicate clearly.</p>

<p>It transforms you from a consumer into a creator.</p>

<p>That shift changed the way I learn forever.</p>

<h2 id="9-your-blog-becomes-a-personal-archive">9. Your Blog Becomes a Personal Archive</h2>

<p>One unexpected benefit of blogging is having a permanent record of your journey.</p>

<p>Looking back at my older articles shows how much I’ve grown.</p>

<p>I can see:</p>

<ul>
  <li>How my thinking evolved.</li>
  <li>What I was learning.</li>
  <li>Problems I was solving.</li>
  <li>Mistakes I made.</li>
</ul>

<p>The blog becomes more than a collection of articles.</p>

<p>It becomes a timeline of personal growth.</p>

<h2 id="10-small-efforts-compound-over-time">10. Small Efforts Compound Over Time</h2>

<p>When I published my first article, it felt insignificant.</p>

<p>The same was true for my second, third, and tenth article.</p>

<p>But by the time I reached fifty posts, those small efforts had accumulated into something meaningful.</p>

<p>Fifty articles represent:</p>

<ul>
  <li>Hundreds of hours of learning.</li>
  <li>Hundreds of hours of writing.</li>
  <li>Dozens of lessons learned.</li>
  <li>A growing body of work.</li>
</ul>

<p>This reminded me that success is rarely the result of a single big action.</p>

<p>It’s usually the result of many small actions repeated consistently over time.</p>

<h2 id="11-the-real-reward-isnt-traffic">11. The Real Reward Isn’t Traffic</h2>

<p>When people think about blogging success, they often think about page views, followers, and analytics.</p>

<p>Those metrics matter, but they’re not the greatest reward.</p>

<p>The greatest reward is who you become during the process.</p>

<p>Blogging made me:</p>

<ul>
  <li>A better writer.</li>
  <li>A better learner.</li>
  <li>A better communicator.</li>
  <li>A more disciplined creator.</li>
  <li>A more thoughtful developer.</li>
</ul>

<p>Those benefits are far more valuable than any traffic number.</p>

<h2 id="looking-ahead">Looking Ahead</h2>

<p>Reaching 72 blog posts feels like an important milestone, but it also feels like the beginning.</p>

<p>There’s still so much to learn.</p>

<p>So many topics to explore.</p>

<p>So many experiences to share.</p>

<p>The goal isn’t simply to publish more articles.</p>

<p>The goal is to continue learning, improving, and documenting the journey.</p>

<p>If the first 72 posts taught me anything, it’s that growth comes from consistency, curiosity, and the willingness to keep showing up.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Publishing my first 72 blog posts taught me lessons that extend far beyond writing.</p>

<p>It taught me patience when growth was slow.</p>

<p>It taught me discipline when motivation disappeared.</p>

<p>It taught me confidence when self-doubt appeared.</p>

<p>Most importantly, it taught me that meaningful progress is often the result of small actions repeated consistently over time.</p>

<p>If you’re thinking about starting a blog, my advice is simple:</p>

<p>Start now.</p>

<p>Don’t wait until you’re an expert.</p>

<p>Don’t wait until everything is perfect.</p>

<p>Write your first post.</p>

<p>Then write your second.</p>

<p>Then your third.</p>

<p>One day, you’ll look back and realize those small steps created something much bigger than you ever imagined.</p>

<p>I did an article about <a href="https://blogs.innova.co.ke/importance-of-writing-for-devs/">importance of writing for developers</a> which you can check out.</p>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/72-blogs-later.jpg" medium="image" />
  
  
    
      <category>Writing</category>
    
      <category>Blogging</category>
    
      <category>Career</category>
    
  
  
    
      <category>writing</category>
    
      <category>blogging</category>
    
      <category>career advice</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Debugging Is a Skill Nobody Teaches You</title>
      <link>https://billyokeyo.dev/posts/debugging-is-a-skill-noone-teaches-you/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/debugging-is-a-skill-noone-teaches-you/</guid>
      <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-05-05T12:02:23+00:00</atom:updated>
  
      <description><![CDATA[Debugging is a critical skill that many developers struggle with. This post breaks down the mindset and process of effective debugging, with practical steps and real examples to help you become a better problem solver.]]></description>
      <content:encoded><![CDATA[<blockquote>
  <p><em>You’ve been staring at the same bug for 2 hours.</em>
You’ve restarted the server. Cleared cache. Added random <code>console.log</code>s.
Somehow… it still doesn’t work.</p>
</blockquote>

<p>At some point, you stop coding and start guessing.</p>

<p><img src="https://media.giphy.com/media/3o7btPCcdNniyf0ArS/giphy.gif" alt="Frustrated Coding" /></p>

<p>And that’s the real problem.</p>

<blockquote>
  <p><strong>The issue isn’t the bug.
It’s that nobody actually teaches debugging as a skill.</strong></p>
</blockquote>

<hr />

<h2 id="the-way-most-developers-debug">The Way Most Developers Debug</h2>

<p>Let’s be honest. Most of us learned debugging like this:</p>

<ul>
  <li>Sprinkle <code>console.log</code> everywhere</li>
  <li>Change random lines and hope something works</li>
  <li>Copy-paste error messages into Google</li>
  <li>Restart everything “just in case”</li>
</ul>

<p><img src="https://media.giphy.com/media/13HgwGsXF0aiGY/giphy.gif" alt="Random Typing" /></p>

<p>It <em>sometimes</em> works.</p>

<p>But it’s slow, frustrating, and unreliable.</p>

<p>It’s not debugging.</p>

<p>It’s <strong>trial and error disguised as progress</strong>.</p>

<hr />

<h2 id="what-debugging-actually-is">What Debugging Actually Is</h2>

<p>Here’s the mindset shift that changes everything:</p>

<blockquote>
  <p><strong>Debugging is not about fixing code.
It’s about finding where your mental model diverges from reality.</strong></p>
</blockquote>

<p>You <em>think</em> the system works one way.</p>

<p>Reality says otherwise.</p>

<p>Your job is to <strong>close that gap</strong>.</p>

<hr />

<h2 id="the-debugging-mindset">The Debugging Mindset</h2>

<p>Before tools, before techniques, this is what matters most.</p>

<h3 id="1-assume-your-assumptions-are-wrong">1. Assume Your Assumptions Are Wrong</h3>

<p>If something doesn’t work, at least one thing you believe is false.</p>

<p>Your job is to find it.</p>

<hr />

<h3 id="2-narrow-the-problem-space">2. Narrow the Problem Space</h3>

<p>Bad debugging:</p>

<blockquote>
  <p>“Something is wrong with the app”</p>
</blockquote>

<p>Good debugging:</p>

<blockquote>
  <p>“The issue happens only when this function runs after login”</p>
</blockquote>

<p><img src="https://media.giphy.com/media/l0IylOPCNkiqOgMyA/giphy.gif" alt="Analyzing Clues" /></p>

<hr />

<h3 id="3-reproduce-before-fixing">3. Reproduce Before Fixing</h3>

<p>If you can’t reliably reproduce the bug, you don’t understand it.</p>

<p>And if you don’t understand it, your fix is luck—not skill.</p>

<hr />

<h3 id="4-one-change-at-a-time">4. One Change at a Time</h3>

<p>If you change 5 things and it works…
which one fixed it?</p>

<p>You don’t know.</p>

<p>That’s how bugs come back later.</p>

<hr />

<h3 id="5-understand-before-you-patch">5. Understand Before You Patch</h3>

<p>Quick fixes feel good.</p>

<p>Understanding the root cause makes you dangerous (in a good way).</p>

<hr />

<h2 id="a-repeatable-debugging-process">A Repeatable Debugging Process</h2>

<p>This is where things become practical.</p>

<hr />

<h3 id="step-1-reproduce-the-bug"><strong>Step 1: Reproduce the Bug</strong></h3>

<p>Make it happen consistently.</p>

<pre><code class="language-bash">Click button → error appears  
Refresh → still happens  
Different browser → still happens
</code></pre>

<p>If it’s inconsistent, your first task is to <strong>find the pattern</strong>.</p>

<hr />

<h3 id="step-2-define-expected-vs-actual"><strong>Step 2: Define Expected vs Actual</strong></h3>

<p>Write it down clearly.</p>

<pre><code class="language-text">Expected: API returns user data  
Actual: API returns empty array
</code></pre>

<p>This step alone eliminates confusion.</p>

<hr />

<h3 id="step-3-isolate-the-problem"><strong>Step 3: Isolate the Problem</strong></h3>

<p>Shrink the scope.</p>

<ul>
  <li>Comment out unrelated code</li>
  <li>Remove layers (UI → API → DB)</li>
  <li>Test pieces independently</li>
</ul>

<p>Think of it like this:</p>

<pre><code>[ UI ] → [ API ] → [ Database ]

Which layer is lying?
</code></pre>

<hr />

<h3 id="step-4-form-a-hypothesis"><strong>Step 4: Form a Hypothesis</strong></h3>

<p>Be explicit:</p>

<blockquote>
  <p>“I think the API is returning empty data because the query filter is wrong.”</p>
</blockquote>

<p>Now you’re not guessing—you’re <strong>testing a theory</strong>.</p>

<hr />

<h3 id="step-5-test-the-hypothesis"><strong>Step 5: Test the Hypothesis</strong></h3>

<p>Use targeted tools:</p>

<ul>
  <li>Logs</li>
  <li>Breakpoints</li>
  <li>Network inspector</li>
</ul>

<p><img src="https://media.giphy.com/media/26ufdipQqU2lhNA4g/giphy.gif" alt="Experimenting" /></p>

<p>Example:</p>

<pre><code class="language-js">console.log("User ID:", userId)
</code></pre>

<p>But intentional—not random.</p>

<hr />

<h3 id="step-6-fix-and-verify"><strong>Step 6: Fix and Verify</strong></h3>

<p>Fix it.</p>

<p>Then confirm:</p>

<ul>
  <li>Does it work in all cases?</li>
  <li>Did you break something else?</li>
</ul>

<p><img src="https://media.giphy.com/media/26gsspfbt1HfVQ9va/giphy.gif" alt="Calm Focus" /></p>

<hr />

<h3 id="step-7-understand-the-root-cause"><strong>Step 7: Understand the Root Cause</strong></h3>

<p>This is where most devs stop too early.</p>

<p>Don’t just fix it—<strong>explain it</strong>:</p>

<blockquote>
  <p>“The bug happened because the state updated asynchronously, and we read it too early.”</p>
</blockquote>

<p>Now you’ve learned something reusable.</p>

<hr />

<h2 id="real-example-the-api-is-broken-but-its-not">Real Example: “The API Is Broken” (But It’s Not)</h2>

<p>Let’s walk through a real scenario.</p>

<hr />

<h3 id="the-bug">The Bug</h3>

<blockquote>
  <p>Frontend shows: <strong>No data available</strong></p>
</blockquote>

<hr />

<h3 id="initial-assumption">Initial Assumption</h3>

<blockquote>
  <p>“The API is broken.”</p>
</blockquote>

<hr />

<h3 id="step-1-check-network-tab">Step 1: Check Network Tab</h3>

<p>You open DevTools → Network:</p>

<p>API returns correct data</p>

<p>So… not the API.</p>

<hr />

<h3 id="step-2-check-state">Step 2: Check State</h3>

<pre><code class="language-js">console.log(data)
</code></pre>

<p>It logs:</p>

<pre><code class="language-js">[]
</code></pre>

<p>Empty array.</p>

<hr />

<h3 id="step-3-trace-the-flow">Step 3: Trace the Flow</h3>

<pre><code class="language-js">useEffect(() =&gt; {
  fetchData()
}, [])
</code></pre>

<p>Inside <code>fetchData</code>:</p>

<pre><code class="language-js">setData(response.data)
console.log(data) // still empty
</code></pre>

<hr />

<h3 id="the-problem">The Problem</h3>

<p>React state updates are <strong>asynchronous</strong>.</p>

<p>You’re logging <strong>before state updates</strong>.</p>

<hr />

<h3 id="the-fix">The Fix</h3>

<pre><code class="language-js">useEffect(() =&gt; {
  fetchData()
}, [])

useEffect(() =&gt; {
  console.log(data)
}, [data])
</code></pre>

<h2><img src="https://media.giphy.com/media/111ebonMs90YLu/giphy.gif" alt="Victory" /></h2>

<h3 id="the-lesson">The Lesson</h3>

<blockquote>
  <p>The bug wasn’t in the API.
It was in your mental model of how state updates work.</p>
</blockquote>

<hr />

<h2 id="tools-that-actually-help">Tools That Actually Help</h2>

<p>Not everything is about tools—but the right ones matter.</p>

<hr />

<h3 id="1-browser-devtools-underrated-powerhouse">1. Browser DevTools (Underrated Powerhouse)</h3>

<ul>
  <li><strong>Network tab</strong> → verify API calls</li>
  <li><strong>Console</strong> → inspect runtime values</li>
  <li><strong>Application tab</strong> → check storage</li>
</ul>

<hr />

<h3 id="2-breakpoints-game-changer">2. Breakpoints (Game Changer)</h3>

<p>Instead of spamming logs:</p>

<p>Pause execution and inspect state <em>live</em></p>

<hr />

<h3 id="3-intentional-logging">3. Intentional Logging</h3>

<p>Bad:</p>

<pre><code class="language-js">console.log("here")
</code></pre>

<p>Good:</p>

<pre><code class="language-js">console.log("User after login:", user)
</code></pre>

<hr />

<h3 id="4-stack-traces">4. Stack Traces</h3>

<p>Read them.</p>

<p>They literally tell you:</p>

<ul>
  <li>Where the error happened</li>
  <li>What triggered it</li>
</ul>

<hr />

<h3 id="5-rubber-duck-debugging">5. Rubber Duck Debugging</h3>

<p>Explain the bug out loud.</p>

<p>Yes, seriously.</p>

<p>You’ll often solve it mid-explanation.</p>

<hr />

<pre><code>Problem → Hypothesis → Test → Learn → Repeat
</code></pre>

<hr />

<h2 id="common-debugging-traps">Common Debugging Traps</h2>

<p>Avoid these and you’ll already be ahead of most devs:</p>

<hr />

<h3 id="fixing-symptoms-instead-of-causes">Fixing Symptoms Instead of Causes</h3>

<p>You silence the error… but the bug is still there.</p>

<hr />

<h3 id="changing-too-many-things-at-once">Changing Too Many Things at Once</h3>

<p>Now you don’t know what worked.</p>

<hr />

<h3 id="ignoring-error-messages">Ignoring Error Messages</h3>

<p>The error is literally telling you what’s wrong.</p>

<p>Read it.</p>

<hr />

<h3 id="assuming-the-bug-is-weird">Assuming the Bug Is “Weird”</h3>

<p>It’s almost never weird.</p>

<p>It’s misunderstood.</p>

<hr />

<h3 id="it-works-on-my-machine">“It Works on My Machine”</h3>

<p>This is not a flex.</p>

<p>It’s a clue.</p>

<hr />

<h2 id="a-better-mental-model">A Better Mental Model</h2>

<pre><code>Expectation ≠ Reality  
        ↓  
Investigate the gap
</code></pre>

<p>That’s debugging.</p>

<hr />

<h2 id="final-thought">Final Thought</h2>

<blockquote>
  <p>The best developers aren’t the ones who write perfect code.
They’re the ones who can <strong>quickly understand why things break</strong>.</p>
</blockquote>

<p>Debugging isn’t a side skill.</p>

<p>It <em>is</em> the job.</p>

]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/find-the-bug.png" medium="image" />
  
  
    
      <category>Programming</category>
    
      <category>Software Development</category>
    
      <category>Career</category>
    
  
  
    
      <category>debugging</category>
    
      <category>software development</category>
    
      <category>programming</category>
    
      <category>career advice</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Stripe Connect on Accounts v2 — Standard, Express, and Custom, Rebuilt Around Configurations (with HTTP, Python, C#, and PHP)</title>
      <link>https://billyokeyo.dev/posts/stripe-connect-accounts-v2/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/stripe-connect-accounts-v2/</guid>
      <pubDate>Mon, 06 Apr 2026 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-04-22T09:22:08+00:00</atom:updated>
  
      <description><![CDATA[A Connect integration guide aligned with Stripe’s Accounts v2 API: merchant, customer, and recipient configurations on one Account, customer_account vs Customer, and how Standard / Express / Custom maps onto the new model—with examples and links to official docs.]]></description>
      <content:encoded><![CDATA[<p>This is the <strong>Accounts v2</strong> companion to the <strong><a href="https://billyokeyo.dev/posts/integrating-to-stripe/">original Connect guide (Accounts v1)</a></strong>. Same <strong>platform concepts</strong>—Standard, Express, Custom, money flow, compliance—but the <strong>API shape</strong> is the one Stripe describes in <strong><a href="https://docs.stripe.com/connect/accounts-v2">Connect and the Accounts v2 API</a></strong>. Use the older post when you must stay on <strong>v1</strong> (<code>Account.create</code> with <code>type=express</code>, OAuth-only Standard flows, etc.). Use <strong>this</strong> post when you are designing around <strong>one <code>Account</code></strong>, <strong>configurations</strong>, and <strong><code>customer_account</code></strong>.</p>

<blockquote>
  <p class="prompt-tip"><strong>Heads-up:</strong> v2 uses <strong>different endpoints and JSON</strong> than classic <code>Accounts</code> CRUD. Always confirm <strong>API version</strong>, <strong>preview flags</strong>, and <strong>SDK support</strong> in <strong><a href="https://docs.stripe.com/connect/accounts-v2">Stripe’s documentation</a></strong> before shipping production code.</p>
</blockquote>

<h2 id="what-is-stripe-connect">What is Stripe Connect?</h2>

<p>Stripe Connect is for <strong>platforms</strong> that move money between <strong>buyers</strong>, <strong>sellers</strong>, and <strong>your platform</strong>. You connect <strong>connected accounts</strong> to your <strong>platform account</strong> so you can:</p>

<ul>
  <li>Collect payments from customers</li>
  <li>Pay out to sellers or service providers</li>
  <li>Take <strong>application fees</strong> where appropriate</li>
</ul>

<p>Stripe holds the <strong>payments</strong> rail; you own <strong>product and UX</strong> choices.</p>

<h2 id="two-ideas-at-once-style-standard--express--custom-and-shape-v2-configurations">Two ideas at once: “style” (Standard / Express / Custom) and “shape” (v2 configurations)</h2>

<p>The <strong><a href="https://billyokeyo.dev/posts/integrating-to-stripe/">original guide</a></strong> compares <strong>Standard</strong>, <strong>Express</strong>, and <strong>Custom</strong> as <strong>who owns onboarding, dashboards, and compliance</strong>.</p>

<p><strong>Accounts v2</strong> adds a separate axis: <strong>what capabilities</strong> a single <strong><code>Account</code></strong> has, expressed as <strong>configurations</strong>:</p>

<table>
  <thead>
    <tr>
      <th>Configuration (v2)</th>
      <th>Role in plain language</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong><code>merchant</code></strong></td>
      <td>Accept card payments, get paid out—what you usually wanted from a<strong>connected account</strong> selling on your platform.</td>
    </tr>
    <tr>
      <td><strong><code>customer</code></strong></td>
      <td>Be<strong>billed like a customer</strong> (subscriptions, invoices to your platform) using the <strong>same</strong> Account identity—often replacing a separate <strong>Customer</strong> object for that business.</td>
    </tr>
    <tr>
      <td><strong><code>recipient</code></strong></td>
      <td>Receive<strong>transfers</strong> (e.g. indirect charges), using the v2 <strong>transfer</strong> capabilities Stripe documents for recipients.</td>
    </tr>
  </tbody>
</table>

<p>You can assign <strong>one or more</strong> configurations to the <strong>same</strong> <code>Account</code>. That is the core promise of v2: <strong>one identity</strong>, <strong>multiple roles</strong>, <strong>less manual ID mapping</strong>.</p>

<p>The <strong>Standard / Express / Custom</strong> choice is still <strong>real</strong>: it describes <strong>OAuth vs Stripe-hosted onboarding vs fully custom UI</strong>. On v2 you implement those <strong>experiences</strong> while creating and updating <strong>Accounts</strong> through the <strong>v2</strong> surface (where supported).</p>

<h2 id="standard-vs-express-vs-custom-unchanged-product-trade-offs">Standard vs Express vs Custom (unchanged product trade-offs)</h2>

<table>
  <thead>
    <tr>
      <th>Feature / aspect</th>
      <th><strong>Standard</strong></th>
      <th><strong>Express</strong></th>
      <th><strong>Custom</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Who owns the Stripe relationship</strong></td>
      <td>The user’s<strong>existing</strong> Stripe user; you connect via <strong>OAuth</strong>.</td>
      <td>You create<strong>connected accounts</strong>; Stripe hosts <strong>onboarding</strong> and a <strong>light</strong> dashboard.</td>
      <td>You own<strong>all</strong> UX; Stripe is invisible to end users.</td>
    </tr>
    <tr>
      <td><strong>KYC / onboarding</strong></td>
      <td>User uses<strong>Stripe Dashboard</strong>.</td>
      <td><strong>Stripe-hosted</strong> onboarding (Account Links, etc., per current docs).</td>
      <td><strong>You</strong> collect data and satisfy Stripe requirements via API.</td>
    </tr>
    <tr>
      <td><strong>Dashboard</strong></td>
      <td>Full<strong>Stripe</strong> dashboard for the user.</td>
      <td><strong>Express</strong> dashboard.</td>
      <td><strong>None</strong> unless you build it.</td>
    </tr>
    <tr>
      <td><strong>Best when</strong></td>
      <td>Sellers<strong>already</strong> have Stripe.</td>
      <td>You need<strong>speed</strong> and shared compliance.</td>
      <td>You need<strong>full</strong> branding and control.</td>
    </tr>
  </tbody>
</table>

<p>On <strong>v2</strong>, you still make these <strong>product</strong> choices—but you attach <strong>merchant</strong> / <strong>customer</strong> / <strong>recipient</strong> <strong>configurations</strong> to match what each connected business must do.</p>

<h2 id="architectural-overview">Architectural overview</h2>

<p>Fund flow is unchanged at a high level:</p>

<p><strong>What changes in v2</strong> is <strong>object modeling</strong>:</p>

<ul>
  <li>One <strong><code>Account</code></strong> can represent <strong>both</strong> “seller” and “buyer of your SaaS” if you add <strong><code>merchant</code></strong> and <strong><code>customer</code></strong> configurations.</li>
  <li>APIs that took <strong><code>customer</code></strong> may accept <strong><code>customer_account</code></strong> with an <strong>Account</strong> ID—see <strong><a href="https://docs.stripe.com/connect/use-accounts-as-customers">using Accounts as customers</a></strong>.</li>
</ul>

<h2 id="accounts-v2-primitives-before-code">Accounts v2 primitives (before code)</h2>

<p>Stripe’s v2 <strong><code>Account</code></strong> objects differ from v1’s flat <code>Account</code> create. You typically send:</p>

<ul>
  <li><strong><code>identity</code></strong> — country, entity type, business details.</li>
  <li><strong><code>configuration</code></strong> — nested blocks such as <strong><code>merchant</code></strong>, <strong><code>customer</code></strong>, <strong><code>recipient</code></strong>, each with <strong>capabilities</strong> you request (<code>card_payments</code>, balance / payout capabilities as named in current docs).</li>
  <li><strong><code>defaults</code></strong> — currency, locales, responsibilities (fees / losses collector), etc.</li>
  <li><strong><code>include</code></strong> — ask the API to return nested sections (e.g. <code>configuration.merchant</code>, <code>requirements</code>).</li>
</ul>

<p>Responses may <strong>omit</strong> fields unless you <strong><code>include</code></strong> them—see Stripe’s note on <strong><a href="https://docs.stripe.com/api-includable-response-values">includable response values</a></strong>.</p>

<p><strong>API version:</strong> v2 calls often require a specific <strong><code>Stripe-Version</code></strong> header (including <strong>preview</strong> versions while the API evolves). Set this from the <strong>exact</strong> value in <strong><a href="https://docs.stripe.com/connect/accounts-v2">Stripe’s Accounts v2 docs</a></strong> for your integration window.</p>

<h2 id="implementation-creating-an-account-on-v2-http-first">Implementation: creating an Account on v2 (HTTP-first)</h2>

<p>Official examples are <strong>REST + JSON</strong>. Below is <strong>illustrative</strong>—copy <strong>field names</strong>, <strong>capability keys</strong>, and <strong>version</strong> from the current docs, not from this blog alone.</p>

<h3 id="curl-canonical-pattern-from-stripe">cURL (canonical pattern from Stripe)</h3>

<p>Stripe documents <strong><code>POST /v2/core/accounts</code></strong> with a JSON body. Conceptually:</p>

<pre><code class="language-bash">curl -X POST https://api.stripe.com/v2/core/accounts \
  -H "Authorization: Bearer sk_test_..." \
  -H "Stripe-Version: &lt;version-from-stripe-docs&gt;" \
  -H "Content-Type: application/json" \
  --data '{
    "contact_email": "seller@example.com",
    "display_name": "Example Seller",
    "identity": {
      "country": "us",
      "entity_type": "company",
      "business_details": { "registered_name": "Example Co" }
    },
    "configuration": {
      "merchant": {
        "capabilities": {
          "card_payments": { "requested": true }
        }
      },
      "customer": {
        "capabilities": {
          "automatic_indirect_tax": { "requested": true }
        }
      }
    },
    "defaults": {
      "currency": "usd",
      "responsibilities": {
        "fees_collector": "stripe",
        "losses_collector": "stripe"
      },
      "locales": ["en-US"]
    },
    "include": [
      "configuration.customer",
      "configuration.merchant",
      "identity",
      "requirements"
    ]
  }'
</code></pre>

<p>This shows the <strong>mental model</strong>: <strong>one</strong> create, <strong>multiple</strong> configurations, <strong>explicit</strong> includes.</p>

<h3 id="python-generic-http-client">Python (generic HTTP client)</h3>

<p>Until your <strong><code>stripe</code></strong> SDK exposes stable helpers for every v2 path, <strong><code>httpx</code></strong> or <strong><code>requests</code></strong> keeps you aligned with the docs:</p>

<pre><code class="language-python">import os
import requests

def create_account_v2():
    r = requests.post(
        "https://api.stripe.com/v2/core/accounts",
        headers={
            "Authorization": f"Bearer {os.environ['STRIPE_SECRET_KEY']}",
            "Stripe-Version": "&lt;version-from-stripe-docs&gt;",
            "Content-Type": "application/json",
        },
        json={
            # ...same structure as the cURL example...
        },
        timeout=30,
    )
    r.raise_for_status()
    return r.json()
</code></pre>

<h3 id="php-laravel-style">PHP (Laravel-style)</h3>

<p>Use <strong>Guzzle</strong> or Laravel <strong><code>Http::withHeaders([...])-&gt;post(...)</code></strong> with the same URL, <strong><code>Stripe-Version</code></strong>, and JSON body. Keep <strong>secrets</strong> in <strong>config</strong>, not in source control.</p>

<h3 id="c-net">C# (.NET)</h3>

<p>Use <strong><code>HttpClient</code></strong> with <strong><code>StringContent(json, Encoding.UTF8, "application/json")</code></strong> and the same headers. Deserialize the JSON response into your own DTOs that track <strong><code>id</code></strong>, <strong><code>requirements</code></strong>, and <strong><code>configuration.*</code></strong> state.</p>

<blockquote>
  <p class="prompt-tip"><strong>OAuth / Standard accounts:</strong> Stripe currently directs platforms that authenticate with <strong>OAuth</strong> to connected accounts to <strong>continue using v1</strong> for that path. Treat <strong>Standard</strong> as <strong>documented in the <a href="https://billyokeyo.dev/posts/integrating-to-stripe/">v1 guide</a></strong> until your OAuth + v2 story is explicitly supported for your use case—see <strong><a href="https://docs.stripe.com/connect/accounts-v2">Accounts v2</a></strong> and <strong><a href="https://docs.stripe.com/stripe-apps/api-authentication/oauth">OAuth</a></strong>.</p>
</blockquote>

<h2 id="onboarding-links-express-style-flows">Onboarding links (Express-style flows)</h2>

<p>For <strong>Express-like</strong> experiences you still send users through <strong>Stripe-hosted onboarding</strong> where the product allows it. With a <strong>connected account ID</strong> returned from v2 (<code>acct_...</code>), <strong><code>Account Links</code></strong> (v1 resource) remain the usual tool for <strong><code>account_onboarding</code></strong>—the same pattern as the <strong><a href="https://billyokeyo.dev/posts/integrating-to-stripe/">v1 article</a></strong>, but the <strong><code>account</code></strong> value may come from a <strong>v2</strong> create. Verify compatibility for your <strong>API version</strong> in Stripe’s docs.</p>

<p><strong>Python (v1 Account Links API, account id from v2 create):</strong></p>

<pre><code class="language-python">import stripe
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]

link = stripe.AccountLink.create(
    account=connected_account_id,
    refresh_url="https://yourapp.com/reauth",
    return_url="https://yourapp.com/complete",
    type="account_onboarding",
)
</code></pre>

<h2 id="custom-style-integrations-on-v2">Custom-style integrations on v2</h2>

<p><strong>Custom</strong> still means: <strong>you</strong> own KYC collection, ToS acceptance, and ongoing verification. On v2 you express that by:</p>

<ul>
  <li>Sending <strong>complete <code>identity</code></strong> data the API requires.</li>
  <li>Requesting <strong><code>merchant</code></strong> / <strong><code>recipient</code></strong> capabilities your product needs.</li>
  <li>Handling <strong><code>requirements</code></strong> and <strong>webhooks</strong> the same way you would for Custom on v1—only the <strong>payload shape</strong> differs.</li>
</ul>

<p>You may still use <strong><code>tos_acceptance</code></strong>-style fields where the v2 schema maps them; follow <strong>Stripe’s v2 reference</strong> for exact property names.</p>

<h2 id="using-accounts-as-customers-customer_account">Using Accounts as customers (<code>customer_account</code>)</h2>

<p>Where you used <strong><code>customer=cus_...</code></strong>, many flows accept <strong><code>customer_account=acct_...</code></strong> for an Account that has the <strong><code>customer</code></strong> configuration. Example pattern from Stripe (conceptual):</p>

<pre><code class="language-bash">curl https://api.stripe.com/v1/setup_intents \
  -u "sk_test_...:" \
  -H "Stripe-Version: &lt;version-from-stripe-docs&gt;" \
  -d customer_account=acct_123 \
  -d "payment_method_types[]=card" \
  -d confirm=true \
  -d usage=off_session
</code></pre>

<p>Details and supported objects live under <strong><a href="https://docs.stripe.com/connect/use-accounts-as-customers">using Accounts as customers</a></strong>.</p>

<h2 id="checking-balances">Checking balances</h2>

<p>For many <strong>Connect</strong> operations, <strong>connected account</strong> scoping is unchanged. <strong>Python:</strong></p>

<pre><code class="language-python">balance = stripe.Balance.retrieve(stripe_account=account_id)
</code></pre>

<p><strong>PHP:</strong></p>

<pre><code class="language-php">$balance = \Stripe\Balance::retrieve([], ['stripe_account' =&gt; $accountId]);
</code></pre>

<p><strong>C#:</strong></p>

<pre><code class="language-csharp">var balance = await stripe.Balance.GetAsync(
    new BalanceGetOptions(),
    new RequestOptions { StripeAccount = accountId }
);
</code></pre>

<p>Confirm in Stripe’s docs whether your <strong>v2</strong> account IDs behave identically for every <strong>Balance</strong> and <strong>v1</strong> helper you rely on during migration.</p>

<h2 id="compliance-responsibilities">Compliance responsibilities</h2>

<p>The <strong>Standard / Express / Custom</strong> compliance split from the <strong><a href="https://billyokeyo.dev/posts/integrating-to-stripe/">original article</a></strong> still applies <strong>who</strong> collects KYC and <strong>who</strong> owns disputes. <strong>v2</strong> can <strong>reduce duplicate</strong> identity collection when you <strong>add</strong> configurations to an <strong>existing</strong> Account instead of opening a second <strong>Customer</strong> record.</p>

<table>
  <thead>
    <tr>
      <th>Responsibility</th>
      <th>Standard</th>
      <th>Express</th>
      <th>Custom</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>KYC</td>
      <td>Stripe</td>
      <td>Mostly Stripe</td>
      <td>You</td>
    </tr>
    <tr>
      <td>Tax reporting</td>
      <td>Stripe-heavy</td>
      <td>Shared</td>
      <td>Often you</td>
    </tr>
    <tr>
      <td>PCI</td>
      <td>Stripe-hosted elements</td>
      <td>Shared</td>
      <td>Mostly you</td>
    </tr>
    <tr>
      <td>Disputes</td>
      <td>Stripe-heavy</td>
      <td>Shared</td>
      <td>Often you</td>
    </tr>
  </tbody>
</table>

<h2 id="choosing-a-path">Choosing a path</h2>

<table>
  <thead>
    <tr>
      <th>Scenario</th>
      <th>Style to favor</th>
      <th>v2 angle</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sellers already on Stripe; OAuth</td>
      <td><strong>Standard</strong></td>
      <td>Often<strong>v1 OAuth</strong> until Stripe supports your OAuth + v2 plan</td>
    </tr>
    <tr>
      <td>Fast marketplace onboarding</td>
      <td><strong>Express</strong></td>
      <td><strong>v2</strong> <code>Account</code> + <strong><code>merchant</code></strong> + Account Links</td>
    </tr>
    <tr>
      <td>White-label, embedded finance</td>
      <td><strong>Custom</strong></td>
      <td><strong>v2</strong> full <strong><code>identity</code></strong> + capabilities + your UI</td>
    </tr>
    <tr>
      <td>Same business pays you<em>and</em> sells on your platform</td>
      <td><strong>Express</strong> or <strong>Custom</strong></td>
      <td>Same<strong><code>Account</code></strong>, <strong><code>merchant</code></strong> + <strong><code>customer</code></strong> configurations</td>
    </tr>
  </tbody>
</table>

<h2 id="strategic-considerations">Strategic considerations</h2>

<ul>
  <li><strong>Time-to-market:</strong> Standard (when OAuth fits) &lt; Express &lt; Custom.</li>
  <li><strong>API surface:</strong> v2 adds <strong>configuration</strong> discipline—plan for <strong>migration</strong> from v1, not an eternal split (Stripe <strong>discourages</strong> maintaining both versions simultaneously). - <a href="https://docs.stripe.com/connect/accounts-v2">Reference</a></li>
  <li><strong>SDKs:</strong> Expect to use <strong>HTTP</strong> for some <strong>v2</strong> paths until your language SDK is fully aligned.</li>
</ul>

<h2 id="final-thoughts">Final thoughts</h2>

<p><strong>Accounts v2</strong> does not erase <strong>Standard / Express / Custom</strong>—it <strong>repackages</strong> how you <strong>represent</strong> connected users in the API. Start from <strong><a href="https://docs.stripe.com/connect/accounts-v2">Connect and the Accounts v2 API</a></strong>, add <strong><a href="https://docs.stripe.com/connect/use-accounts-as-customers">using Accounts as customers</a></strong> when the same legal entity both <strong>sells</strong> and <strong>buys</strong> from your platform, and keep the <strong><a href="https://billyokeyo.dev/posts/integrating-to-stripe/">v1 Connect guide</a></strong> handy for <strong>OAuth</strong> flows and <strong>legacy</strong> snippets until you have fully moved.</p>

<p>Either way, you still trade off <strong>control</strong>, <strong>compliance</strong>, and <strong>complexity</strong>—only the <strong>object model</strong> got a long-overdue upgrade.</p>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/stripe-integration.jpg" medium="image" />
  
  
    
      <category>Payment Processing</category>
    
      <category>Software Development</category>
    
  
  
    
      <category>Stripe</category>
    
      <category>Payment Processing</category>
    
      <category>Software Development</category>
    
      <category>APIs</category>
    
      <category>Fintech</category>
    
      <category>Stripe Connect</category>
    
      <category>Accounts v2</category>
    
      <category>Integrating Stripe Laravel</category>
    
      <category>Integrating Stripe C#</category>
    
      <category>Integrating Stripe Python</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Contract Testing: Prevent Breaking Changes Before Production</title>
      <link>https://billyokeyo.dev/posts/contract-testing/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/contract-testing/</guid>
      <pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-06-30T15:43:26+00:00</atom:updated>
  
      <description><![CDATA[Learn how to implement contract testing between services to catch breaking changes before they hit production. Works across any tech stack—examples in .NET and Angular.]]></description>
      <content:encoded><![CDATA[<blockquote>
  <p><em>“It worked locally. Tests passed. But production still broke.”</em></p>
</blockquote>

<p>If you’re building distributed systems with multiple services and frontends, you’ve likely encountered this (whether using <strong>.NET + Angular</strong>, <strong>Node.js + React</strong>, <strong>Python + Vue</strong>, or any combination):</p>

<ul>
  <li>A backend change gets deployed</li>
  <li>The frontend suddenly breaks</li>
  <li>No tests warned you</li>
</ul>

<p>The issue isn’t always logic.</p>

<p>It’s often a <strong>broken contract between systems</strong>.</p>

<h1 id="the-problem-silent-api-breakages">The Problem: Silent API Breakages</h1>

<p>In a typical setup:</p>

<ul>
  <li>Service Provider: An API or microservice</li>
  <li>Service Consumer: A frontend, app, or another service</li>
  <li>Communication: JSON over HTTP (or any protocol)</li>
</ul>

<p>Everything depends on one thing:</p>

<blockquote>
  <p>The consumer and provider agreeing on <strong>what data looks like</strong></p>
</blockquote>

<h2 id="a-real-example">A Real Example</h2>

<p>Let’s use a <strong>.NET API</strong> and <strong>Angular frontend</strong> as an example (though this applies to any tech stack).</p>

<h3 id="api-provider-initial-version">API Provider (Initial Version)</h3>

<pre><code class="language-csharp">public class UserDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}
</code></pre>

<pre><code class="language-csharp">[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
    return Ok(new UserDto
    {
        Id = 1,
        Name = "Billy",
        Email = "billy@example.com"
    });
}
</code></pre>

<h3 id="consumer-interface-angular-example">Consumer Interface (Angular Example)</h3>

<pre><code class="language-ts">export interface User {
  id: number;
  name: string;
  email: string;
}
</code></pre>

<p>Everything works perfectly.</p>

<h2 id="then-a-small-change-happens">Then a “Small” Change Happens</h2>

<pre><code class="language-csharp">public class UserDto
{
    public int Id { get; set; }
    public string FullName { get; set; } // renamed
    public string Email { get; set; }
}
</code></pre>

<h3 id="production-result">Production Result</h3>

<pre><code class="language-ts">user.name //  undefined
</code></pre>

<p>The UI breaks.</p>

<h2 id="why-didnt-traditional-tests-catch-this">Why Didn’t Traditional Tests Catch This?</h2>

<ul>
  <li>Unit tests → passed (backend logic is fine)</li>
  <li>Integration tests → passed (they used the old model)</li>
  <li>The API still returns valid JSON</li>
</ul>

<p>But the <strong>contract between systems changed</strong>—and traditional tests don’t verify that.</p>

<h1 id="what-is-contract-testing">What is Contract Testing?</h1>

<p>In Contract Testing, a contract is a formal specification of expected behavior and communication rules between two or more components, services or systems.</p>

<blockquote>
  <p><strong>Contract testing ensures that your service provider always matches what the consumer expects.</strong></p>
</blockquote>

<p>A <strong>contract</strong> defines:</p>

<ul>
  <li>Request format</li>
  <li>Response structure</li>
  <li>Required fields</li>
  <li>Data types</li>
</ul>

<h2 id="in-simple-terms">In Simple Terms</h2>

<blockquote>
  <p>The <strong>consumer</strong> (frontend, app, or service) defines expectations
The <strong>provider</strong> (API or service) must satisfy them</p>
</blockquote>

<h1 id="consumer-vs-provider">Consumer vs Provider</h1>

<h3 id="consumer">Consumer</h3>

<ul>
  <li>Calls the service/API</li>
  <li>Defines expected structure</li>
  <li>Examples: Angular frontend, React app, mobile app, another microservice</li>
</ul>

<h3 id="provider">Provider</h3>

<ul>
  <li>Returns the data</li>
  <li>Must not break expectations</li>
  <li>Examples: .NET API, Node.js backend, Python service, GraphQL endpoint</li>
</ul>

<h1 id="how-contract-testing-works">How Contract Testing Works</h1>

<p>Instead of relying only on integration tests:</p>

<ol>
  <li>Angular defines expectations</li>
  <li>A contract file is generated</li>
  <li>.NET verifies the contract</li>
</ol>

<h2 id="flow">Flow</h2>

<pre><code>Consumer Test (e.g., Angular)
      ↓
Generates Contract
      ↓
Saved as JSON/YAML
      ↓
Provider verifies against contract (e.g., .NET API)
</code></pre>

<h1 id="practical-example">Practical Example</h1>

<p><em>(.NET backend + Angular frontend as an example)</em></p>

<h2 id="step-1-define-expectations-in-consumer-angular-example">Step 1: Define Expectations in Consumer (Angular example)</h2>

<pre><code class="language-ts">const expectedUser = {
  id: 1,
  name: "Billy",
  email: "billy@example.com"
};

it("should fetch user correctly", async () =&gt; {
  const user = await userService.getUser(1);
  expect(user).toEqual(expectedUser);
});
</code></pre>

<h2 id="generated-contract-simplified">Generated Contract (Simplified)</h2>

<pre><code class="language-json">{
  "request": {
    "method": "GET",
    "path": "/api/users/1"
  },
  "response": {
    "body": {
      "id": 1,
      "name": "Billy",
      "email": "billy@example.com"
    }
  }
}
</code></pre>

<h2 id="step-2-verify-in-provider-net-api-example">Step 2: Verify in Provider (.NET API example)</h2>

<p>Install Pact:</p>

<pre><code class="language-bash">dotnet add package PactNet
</code></pre>

<h3 id="provider-test">Provider Test</h3>

<pre><code class="language-csharp">[Fact]
public void VerifyPact()
{
    var pactVerifier = new PactVerifier();

    pactVerifier
        .ServiceProvider("UserApi", "http://localhost:5000")
        .WithFileSource(new FileInfo("pacts/userapi-angular.json"))
        .Verify();
}
</code></pre>

<h2 id="if-provider-breaks-the-contract">If Provider Breaks the Contract</h2>

<pre><code class="language-csharp">public string FullName { get; set; }
</code></pre>

<ul>
  <li>
    <p>The test fails immediately</p>
  </li>
  <li>
    <p>You catch the issue before deployment</p>
  </li>
</ul>

<h1 id="where-contract-testing-fits">Where Contract Testing Fits</h1>

<pre><code>E2E Tests
(user journeys)

Integration Tests
(service interactions)

Contract Tests 
(API agreements)

Unit Tests
(business logic)
</code></pre>

<h1 id="why-this-matters-in-real-projects">Why This Matters in Real Projects</h1>

<h2 id="safer-refactoring">Safer Refactoring</h2>

<p>Change DTOs, schema, or API responses without fear. Contract tests verify nothing broke.</p>

<h2 id="independent-development">Independent Development</h2>

<p>Consumer and provider teams move faster. Changes are caught instantly, not in production.</p>

<h2 id="faster-debugging">Faster Debugging</h2>

<p>Failures clearly show what broke</p>

<h2 id="stronger-cicd-pipelines">Stronger CI/CD Pipelines</h2>

<pre><code>Consumer Build → Generate Contract
Provider Build → Verify Contract
Deploy → Only if both pass
</code></pre>

<p>This works with any tech stack.</p>

<h1 id="common-mistakes">Common Mistakes</h1>

<h3 id="over-specifying-data">Over-Specifying Data</h3>

<p>Bad:</p>

<pre><code class="language-json">"name": "Billy Okeyo"
</code></pre>

<p>Good:</p>

<pre><code class="language-json">"name": "string"
</code></pre>

<h3 id="testing-everything">Testing Everything</h3>

<p>Only validate fields your frontend actually uses</p>

<h3 id="ignoring-versioning">Ignoring Versioning</h3>

<p>Breaking contracts without versioning leads to production issues</p>

<h1 id="best-practices">Best Practices</h1>

<h3 id="keep-contracts-minimal">Keep Contracts Minimal</h3>

<p>Focus only on required fields</p>

<h3 id="version-your-api">Version Your API</h3>

<pre><code>/api/v1/users
/api/v2/users
</code></pre>

<h3 id="automate-in-cicd">Automate in CI/CD</h3>

<p>Contracts should be generated and verified automatically</p>

<h3 id="use-realistic-data">Use Realistic Data</h3>

<p>Avoid unrealistic mocks</p>

<h1 id="final-takeaway">Final Takeaway</h1>

<blockquote>
  <p>Unit tests verify logic
Integration tests verify systems
E2E tests verify user flows</p>

  <p><strong>Contract tests verify agreements</strong></p>
</blockquote>

<blockquote>
  <p>“Most production bugs aren’t failures… they’re misunderstandings between systems.”</p>
</blockquote>

<p>Contract testing eliminates those misunderstandings.</p>

]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/contract-testing.png" medium="image" />
  
  
    
      <category>Contract Testing</category>
    
      <category>API Testing</category>
    
      <category>Software Architecture</category>
    
  
  
    
      <category>Contract Testing</category>
    
      <category>API Testing</category>
    
      <category>Integration Testing</category>
    
      <category>Microservices</category>
    
      <category>Software Testing</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Why Your Django App Needs Redis and Celery in Production</title>
      <link>https://billyokeyo.dev/posts/why-your-django-app-needs-redis/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/why-your-django-app-needs-redis/</guid>
      <pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-03-16T00:00:00+00:00</atom:updated>
  
      <description><![CDATA[Learn why integrating Redis and Celery into your Django application is essential for handling background tasks efficiently, improving performance, and scaling your app in production.]]></description>
      <content:encoded><![CDATA[<p>Django is an incredibly powerful framework for building web applications quickly. However, as your application grows, certain tasks begin to slow down request-response cycles.</p>

<p>Examples include:</p>

<ul>
  <li>Sending emails</li>
  <li>Generating reports</li>
  <li>Processing uploaded files</li>
  <li>Running background analytics</li>
  <li>Sending notifications</li>
</ul>

<p>Running these tasks during an HTTP request can make your application slow and unreliable.</p>

<p>This is where <strong>Celery and Redis</strong> come in.</p>

<p>Together they allow you to run background jobs asynchronously without blocking your main application.</p>

<hr />

<h2 id="the-problem-with-synchronous-tasks">The Problem with Synchronous Tasks</h2>

<p>Imagine a user submits a request that triggers an operation that takes <strong>10 seconds</strong>.</p>

<p>For example:</p>

<ul>
  <li>Generating a financial report</li>
  <li>Parsing a large document</li>
  <li>Sending multiple emails</li>
</ul>

<p>If your application processes this synchronously:</p>

<pre><code>User Request → Django → Long Task → Response
</code></pre>

<p>The user waits for the entire process to finish.</p>

<p>This leads to:</p>

<ul>
  <li>Slow responses</li>
  <li>Poor user experience</li>
  <li>Possible request timeouts</li>
</ul>

<hr />

<h2 id="introducing-celery">Introducing Celery</h2>

<p>Celery is a <strong>distributed task queue</strong> that allows you to run background jobs outside the request-response cycle.</p>

<p>Instead of executing tasks immediately, Django sends the job to a queue.</p>

<p>A worker then processes it asynchronously.</p>

<p>The flow becomes:</p>

<pre><code>User Request
    ↓
Django
    ↓
Queue Task
    ↓
Immediate Response
    ↓
Celery Worker
    ↓
Executes Job
</code></pre>

<p>This makes your application <strong>fast and scalable</strong>.</p>

<hr />

<h2 id="why-redis">Why Redis?</h2>

<p>Celery requires a <strong>message broker</strong> to manage task queues.</p>

<p>Redis is commonly used because it is:</p>

<ul>
  <li>Extremely fast</li>
  <li>Lightweight</li>
  <li>Easy to deploy</li>
  <li>Perfect for queues and caching</li>
</ul>

<p>Redis stores the tasks until workers pick them up.</p>

<hr />

<h2 id="example-sending-email-in-the-background">Example: Sending Email in the Background</h2>

<p>Instead of sending email directly in a Django view:</p>

<pre><code class="language-python">send_mail(
    "Welcome",
    "Thanks for signing up",
    "noreply@example.com",
    [user.email],
)
</code></pre>

<p>You create a Celery task:</p>

<pre><code class="language-python">from celery import shared_task
from django.core.mail import send_mail

@shared_task
def send_welcome_email(email):
    send_mail(
        "Welcome",
        "Thanks for signing up",
        "noreply@example.com",
        [email],
    )
</code></pre>

<p>Then call it asynchronously:</p>

<pre><code class="language-python">send_welcome_email.delay(user.email)
</code></pre>

<p>The user gets an immediate response while the email is processed in the background.</p>

<hr />

<h2 id="common-use-cases-for-celery">Common Use Cases for Celery</h2>

<p>Celery is useful for many production tasks:</p>

<h3 id="email-sending">Email sending</h3>

<ul>
  <li>Welcome emails</li>
  <li>Notifications</li>
  <li>Password resets</li>
</ul>

<h3 id="data-processing">Data processing</h3>

<ul>
  <li>Financial calculations</li>
  <li>AI processing</li>
  <li>Data pipelines</li>
</ul>

<h3 id="scheduled-tasks">Scheduled tasks</h3>

<ul>
  <li>Daily reports</li>
  <li>Cleaning expired sessions</li>
  <li>Updating analytics</li>
</ul>

<hr />

<h2 id="running-celery-in-production">Running Celery in Production</h2>

<p>A typical Django production stack might look like this:</p>

<pre><code>Users
  ↓
Nginx
  ↓
Gunicorn
  ↓
Django App
  ↓
Redis (Broker)
  ↓
Celery Workers
</code></pre>

<p>Each component plays a role:</p>

<ul>
  <li><strong>Nginx</strong> handles web traffic</li>
  <li><strong>Gunicorn</strong> runs Django</li>
  <li><strong>Redis</strong> manages task queues</li>
  <li><strong>Celery workers</strong> execute background jobs</li>
</ul>

<hr />

<h2 id="scaling-celery-workers">Scaling Celery Workers</h2>

<p>One of Celery’s biggest advantages is scalability.</p>

<p>If tasks increase, you simply add more workers.</p>

<pre><code>celery -A project worker --loglevel=info --concurrency=4
</code></pre>

<p>More workers mean faster task processing.</p>

<hr />

<h2 id="final-thoughts">Final Thoughts</h2>

<p>Celery and Redis are essential tools for running Django applications at scale.</p>

<p>They allow you to:</p>

<ul>
  <li>Improve response times</li>
  <li>Handle heavy workloads</li>
  <li>Build scalable architectures</li>
  <li>Run background processing reliably</li>
</ul>

<p>If your Django application handles tasks that take more than a few seconds, moving them to Celery is one of the best architectural decisions you can make.</p>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/django-redis.png" medium="image" />
  
  
    
      <category>Django</category>
    
      <category>Redis</category>
    
      <category>Celery</category>
    
      <category>Background Tasks</category>
    
  
  
    
      <category>Django</category>
    
      <category>Redis</category>
    
      <category>Celery</category>
    
      <category>Background Tasks</category>
    
      <category>Asynchronous Processing</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>A Practical Guide to File Uploads (Images, Excel, CSV) in Angular + Django</title>
      <link>https://billyokeyo.dev/posts/image-uploads/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/image-uploads/</guid>
      <pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-02-23T00:00:00+00:00</atom:updated>
  
      <description><![CDATA[A comprehensive guide to implementing file uploads for images, CSV, and Excel files using Angular on the frontend and Django with Django REST Framework on the backend, covering validation, security, and best practices.]]></description>
      <content:encoded><![CDATA[<p>File uploads are one of those features that look simple… until they aren’t.</p>

<p>Images need previewing. CSV files need parsing. Excel files need validation. Large files need handling. And suddenly your “simple upload” becomes a full feature.</p>

<p>In this guide, we’ll build a practical and production-ready file upload system using:</p>

<ul>
  <li>Angular (Frontend)</li>
  <li>Django + Django REST Framework (Backend)</li>
</ul>

<p>We’ll cover:</p>

<ol>
  <li>Image uploads with preview</li>
  <li>CSV uploads and parsing</li>
  <li>Excel uploads and processing</li>
  <li>Validation and security best practices</li>
</ol>

<hr />

<h1 id="backend-setup-django--drf">Backend Setup (Django + DRF)</h1>

<h2 id="1-install-dependencies">1. Install Dependencies</h2>

<pre><code class="language-bash">pip install djangorestframework pillow openpyxl pandas
</code></pre>

<ul>
  <li><code>pillow</code> → Image processing</li>
  <li><code>openpyxl</code> → Excel support</li>
  <li><code>pandas</code> → CSV &amp; Excel parsing</li>
</ul>

<hr />

<h2 id="2-configure-media-files">2. Configure Media Files</h2>

<h3 id="settingspy">settings.py</h3>

<pre><code class="language-python">MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
</code></pre>

<h3 id="urlspy-project-level">urls.py (project level)</h3>

<pre><code class="language-python">from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    # your urls
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
</code></pre>

<hr />

<h1 id="part-1-image-upload">Part 1: Image Upload</h1>

<h2 id="django-model">Django Model</h2>

<pre><code class="language-python">from django.db import models

class Profile(models.Model):
    name = models.CharField(max_length=100)
    image = models.ImageField(upload_to='profiles/')
</code></pre>

<hr />

<h2 id="serializer">Serializer</h2>

<pre><code class="language-python">from rest_framework import serializers
from .models import Profile

class ProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = Profile
        fields = '__all__'
</code></pre>

<hr />

<h2 id="view">View</h2>

<pre><code class="language-python">from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework import status

class ProfileUploadView(APIView):
    parser_classes = [MultiPartParser, FormParser]

    def post(self, request):
        serializer = ProfileSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
</code></pre>

<hr />

<h1 id="angular-frontend-image-upload">Angular Frontend (Image Upload)</h1>

<h2 id="html">HTML</h2>

<pre><code class="language-html">&lt;input type="file" (change)="onFileSelected($event)" accept="image/*" /&gt;
&lt;img *ngIf="previewUrl" [src]="previewUrl" width="200" /&gt;
&lt;button (click)="upload()"&gt;Upload&lt;/button&gt;
</code></pre>

<hr />

<h2 id="component">Component</h2>

<pre><code class="language-typescript">selectedFile!: File;
previewUrl: string | ArrayBuffer | null = null;

onFileSelected(event: any) {
  this.selectedFile = event.target.files[0];

  const reader = new FileReader();
  reader.onload = () =&gt; {
    this.previewUrl = reader.result;
  };
  reader.readAsDataURL(this.selectedFile);
}

upload() {
  const formData = new FormData();
  formData.append('name', 'Billy');
  formData.append('image', this.selectedFile);

  this.http.post('http://localhost:8000/api/upload/', formData)
    .subscribe(res =&gt; console.log(res));
}
</code></pre>

<hr />

<h1 id="part-2-csv-upload-and-parsing">Part 2: CSV Upload and Parsing</h1>

<h2 id="django-view-for-csv">Django View for CSV</h2>

<pre><code class="language-python">import pandas as pd
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser

class CSVUploadView(APIView):
    parser_classes = [MultiPartParser]

    def post(self, request):
        file = request.FILES['file']

        if not file.name.endswith('.csv'):
            return Response({'error': 'Invalid file type'}, status=400)

        df = pd.read_csv(file)

        # Example processing
        total_rows = len(df)
        columns = list(df.columns)

        return Response({
            'rows': total_rows,
            'columns': columns
        })
</code></pre>

<hr />

<h1 id="angular-csv-upload">Angular CSV Upload</h1>

<pre><code class="language-typescript">uploadCSV(file: File) {
  const formData = new FormData();
  formData.append('file', file);

  this.http.post('http://localhost:8000/api/upload-csv/', formData)
    .subscribe(res =&gt; console.log(res));
}
</code></pre>

<hr />

<h1 id="part-3-excel-upload-xlsx">Part 3: Excel Upload (.xlsx)</h1>

<h2 id="django-view-for-excel">Django View for Excel</h2>

<pre><code class="language-python">class ExcelUploadView(APIView):
    parser_classes = [MultiPartParser]

    def post(self, request):
        file = request.FILES['file']

        if not file.name.endswith('.xlsx'):
            return Response({'error': 'Invalid file type'}, status=400)

        df = pd.read_excel(file)

        summary = {
            'rows': len(df),
            'columns': list(df.columns)
        }

        return Response(summary)
</code></pre>

<hr />

<h1 id="validation--security-best-practices">Validation &amp; Security Best Practices</h1>

<h2 id="1-limit-file-size">1. Limit File Size</h2>

<p>In settings.py:</p>

<pre><code class="language-python">DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880  # 5MB
</code></pre>

<hr />

<h2 id="2-validate-file-type-properly">2. Validate File Type Properly</h2>

<p>Don’t rely only on extension.</p>

<pre><code class="language-python">if file.content_type not in ['image/jpeg', 'image/png']:
    return Response({'error': 'Invalid image format'}, status=400)
</code></pre>

<hr />

<h2 id="3-rename-uploaded-files">3. Rename Uploaded Files</h2>

<pre><code class="language-python">import uuid

def upload_to(instance, filename):
    ext = filename.split('.')[-1]
    return f"uploads/{uuid.uuid4()}.{ext}"
</code></pre>

<hr />

<h2 id="4-handle-large-files-with-streaming">4. Handle Large Files with Streaming</h2>

<p>For very large CSV files, process in chunks:</p>

<pre><code class="language-python">for chunk in pd.read_csv(file, chunksize=1000):
    # process chunk
    pass
</code></pre>

<hr />

<h1 id="bonus-returning-processed-data">Bonus: Returning Processed Data</h1>

<p>Sometimes you don’t just upload — you process and return a result file.</p>

<p>Example: Generate processed CSV</p>

<pre><code class="language-python">from django.http import HttpResponse

response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="processed.csv"'
df.to_csv(response, index=False)
return response
</code></pre>

<h1 id="final-thoughts">Final Thoughts</h1>

<p>File uploads are not just about saving files.</p>

<p>They are about:</p>

<ul>
  <li>Validation</li>
  <li>Security</li>
  <li>User experience</li>
  <li>Scalability</li>
</ul>

<p>When done correctly, they become powerful data pipelines inside your application.</p>

<p>If you’re building admin systems, reporting tools, learning platforms, or fintech dashboards — mastering uploads is essential.</p>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/file-uploads.png" medium="image" />
  
  
    
      <category>Web Development</category>
    
      <category>Full Stack</category>
    
      <category>Django</category>
    
      <category>Angular</category>
    
  
  
    
      <category>File Uploads</category>
    
      <category>Angular</category>
    
      <category>Django</category>
    
      <category>REST Framework</category>
    
      <category>CSV</category>
    
      <category>Excel</category>
    
      <category>Images</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>The Diderot Effect: A Subtle System Bug in How We Consume Technology</title>
      <link>https://billyokeyo.dev/posts/diderot-effect/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/diderot-effect/</guid>
      <pubDate>Fri, 09 Jan 2026 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-01-09T00:00:00+00:00</atom:updated>
  
      <description><![CDATA[Explore the Diderot Effect and how it subtly influences our technology consumption habits, leading to cascading upgrades and shifting definitions of "enough."]]></description>
      <content:encoded><![CDATA[<p>At the beginning of year (<strong>2026</strong>), I took some time to reflect on my past behavior, not in terms of code quality, architectural decisions, or career milestones, but something far more mundane and quietly expensive: <strong>how I consume technology</strong>.</p>

<p>One pattern stood out immediately.</p>

<p>I bought a <strong>MacBook</strong>, and at the time, it felt sufficient. It solved the problem I had: performance, portability, and a solid development environment. From a purely functional standpoint, nothing else was required.</p>

<p>But that sense of “enough” didn’t persist.</p>

<p>Shortly after, I justified getting an <strong>iPhone</strong> partly for convenience, partly for the ecosystem. Once that was in place, the <strong>Apple Watch</strong> began to feel like a logical extension: notifications, health tracking, tighter integration. And somehow, even after all that, an <strong>Apple TV</strong> entered the setup  as if the system was still incomplete.</p>

<p>Individually, each purchase was defensible. Collectively, they formed a pattern I hadn’t consciously designed.</p>

<p>What struck me during this reflection was not the spending itself, but the realization that <strong>my definition of completeness kept shifting</strong>. Satisfaction behaved like a moving target, always one accessory away.</p>

<p>Later, I came across a concept that explained this behavior with surprising precision: <strong>The Diderot Effect</strong>.</p>

<h2 id="what-is-the-diderot-effect">What Is the Diderot Effect?</h2>

<p>The <strong>Diderot Effect</strong> describes a behavioral phenomenon where <strong>acquiring a new item triggers a cascade of related purchases</strong>, driven by the need for consistency rather than necessity.</p>

<p>The concept originates from <strong>Denis Diderot</strong>, an 18th-century philosopher who, after receiving a luxurious new robe, felt compelled to replace much of his existing furniture because it no longer “matched” the new standard he had introduced.</p>

<p>In modern terms, the Diderot Effect is essentially <strong>scope creep applied to consumption</strong>.</p>

<h2 id="why-the-diderot-effect-resonates-strongly-in-tech">Why the Diderot Effect Resonates Strongly in Tech</h2>

<p>Technology ecosystems are uniquely optimized to amplify this effect.</p>

<h3 id="1-ecosystem-lock-in-as-a-design-strategy">1. Ecosystem Lock-In as a Design Strategy</h3>

<p>Modern tech products are rarely designed as standalone components. They are built as <strong>interdependent systems</strong>:</p>

<ul>
  <li>Phone ↔ laptop ↔ watch ↔ TV</li>
  <li>Cloud services ↔ subscriptions ↔ hardware</li>
  <li>Accessories that unlock “full functionality”</li>
</ul>

<p>Once you introduce a single high-tier component into your stack, the rest of your setup begins to feel like technical debt.</p>

<h3 id="2-identity-driven-tooling">2. Identity-Driven Tooling</h3>

<p>In tech, tools are not just utilities they are signals. Owning certain devices, editors, keyboards, or setups subtly communicates:</p>

<ul>
  <li>Competence</li>
  <li>Professionalism</li>
  <li>“Seriousness” about the craft</li>
</ul>

<p>The internal logic becomes:</p>

<blockquote>
  <p><em>If I’m this kind of developer, my tools should reflect that.</em></p>
</blockquote>

<p>This is where the Diderot Effect stops being about objects and starts being about <strong>identity coherence</strong>.</p>

<h3 id="3-optimization-culture">3. Optimization Culture</h3>

<p>Engineers are trained to optimize systems. Unfortunately, that mindset often spills into consumption:</p>

<ul>
  <li>Latency → upgrade</li>
  <li>Minor friction → replace</li>
  <li>Marginal improvement → justify cost</li>
</ul>

<p>Not every inefficiency needs a hardware solution but the instinct is deeply ingrained.</p>

<h2 id="real-world-tech-examples-of-the-diderot-effect">Real-World Tech Examples of the Diderot Effect</h2>

<ul>
  <li>Buying a new laptop → external monitor → mechanical keyboard → standing desk</li>
  <li>Upgrading a phone → earbuds → watch → chargers in every room</li>
  <li>Improving a dev setup → paid tools → subscriptions → cloud services</li>
</ul>

<p>Each step feels incremental. The aggregate cost rarely does.</p>

<h2 id="the-hidden-costs-engineers-often-overlook">The Hidden Costs Engineers Often Overlook</h2>

<h3 id="1-financial-leakage-through-reasonable-decisions">1. Financial Leakage Through “Reasonable” Decisions</h3>

<p>Most Diderot-driven purchases pass basic rational checks. The problem isn’t irrationality it’s <strong>unbounded justification</strong>.</p>

<h3 id="2-perpetual-dissatisfaction">2. Perpetual Dissatisfaction</h3>

<p>When standards continuously shift upward, stability disappears. You’re always upgrading the environment instead of leveraging it.</p>

<h3 id="3-loss-of-intentional-system-design">3. Loss of Intentional System Design</h3>

<p>Your setup evolves reactively rather than architecturally.</p>

<h2 id="managing-the-diderot-effect-as-a-technical-thinker">Managing the Diderot Effect as a Technical Thinker</h2>

<p>The solution isn’t abstinence it’s <strong>intentional system design</strong>.</p>

<h3 id="1-introduce-decision-latency">1. Introduce Decision Latency</h3>

<p>Treat secondary purchases like production changes:</p>

<ul>
  <li>Add a delay (48 hours, a week, or a sprint)</li>
  <li>Re-evaluate the actual requirement</li>
</ul>

<p>Most impulses decay with time.</p>

<h3 id="2-define-system-boundaries">2. Define System Boundaries</h3>

<p>Before upgrading, explicitly define the scope:</p>

<blockquote>
  <p>“This purchase solves <em>this</em> problem. Nothing else changes.”</p>
</blockquote>

<p>Boundaries prevent cascade failures.</p>

<h3 id="3-separate-functional-debt-from-aesthetic-debt">3. Separate Functional Debt from Aesthetic Debt</h3>

<p>Ask:</p>

<ul>
  <li>Is this blocking productivity?</li>
  <li>Or does it simply look inferior relative to a new baseline?</li>
</ul>

<p>Most Diderot chains are triggered by aesthetics masquerading as inefficiency.</p>

<h3 id="4-budget-upgrades-as-projects-not-reactions">4. Budget Upgrades as Projects, Not Reactions</h3>

<p>Instead of ad-hoc improvements:</p>

<ul>
  <li>Plan upgrades quarterly or annually</li>
  <li>Allocate fixed budgets</li>
  <li>Close the project when scope is met</li>
</ul>

<p>No silent expansions.</p>

<h3 id="5-be-wary-of-identity-based-justifications">5. Be Wary of Identity-Based Justifications</h3>

<p>Phrases like:</p>

<ul>
  <li>“At my level, I should have…”</li>
  <li>“This setup doesn’t reflect where I am now…”</li>
</ul>

<p>These are signals that the Diderot Effect is driving the decision, not necessity.</p>

<h2 id="when-the-diderot-effect-can-be-used-intentionally">When the Diderot Effect Can Be Used Intentionally</h2>

<p>Not all cascades are bad.</p>

<p>Applied deliberately, the effect can reinforce positive systems:</p>

<ul>
  <li>Fitness → nutrition → sleep optimization</li>
  <li>Learning → better tools → deeper focus</li>
  <li>Workspace upgrades that genuinely reduce friction</li>
</ul>

<p>The difference lies in <strong>conscious orchestration</strong> versus unconscious drift.</p>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>The Diderot Effect is not a failure of discipline, it’s a <strong>predictable system behavior</strong> triggered by introducing a higher standard into an existing environment.</p>

<p>In tech, where optimization and cohesion are prized, this effect is especially potent.</p>

<p>Reflecting on my own experience, from a single MacBook purchase to a fully integrated ecosystem  made one thing clear:
<strong>“Enough” must be explicitly defined, or it will keep moving.</strong></p>

<p>As engineers, we design systems for a living.
Our consumption habits deserve the same level of intentionality.</p>

]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/diderot-effect.jpg" medium="image" />
  
  
    
      <category>Personal Development</category>
    
      <category>Technology</category>
    
      <category>Behavioral Economics</category>
    
  
  
    
      <category>Diderot Effect</category>
    
      <category>Consumption</category>
    
      <category>Behavioral Economics</category>
    
      <category>Technology</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>2025 in Review: A Year of Growth, Resilience, and Practical Engineering</title>
      <link>https://billyokeyo.dev/posts/year-review/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/year-review/</guid>
      <pubDate>Mon, 29 Dec 2025 00:00:00 +0000</pubDate>
  
      <atom:updated>2025-12-29T00:00:00+00:00</atom:updated>
  
      <description><![CDATA[Reflecting on the key engineering lessons, articles, and themes from 2025 — covering system design, testing strategies, performance optimizations, engineering culture, and real-world case studies.]]></description>
      <content:encoded><![CDATA[<p><em>Who would’ve thought I’d open my editor on the 29th of December not to debug production or chase a failing test—but to write this recap? Because apparently, I enjoy explaining bugs to the internet.</em></p>

<p>If you told me at the beginning of 2025 that I’d spend the year writing about <strong>scalable systems, testing strategies, outages, performance optimizations, and developer excuses</strong> I would’ve believed you.
If you also told me I’d still be debugging things that “worked yesterday,” I would’ve believed you even faster.</p>

<p>This article is a recap of everything I wrote this year: the lessons, the wins, the bugs, and the “how did this even pass code review?” moments. Think of it as a <strong>Spotify Wrapped</strong>, but for technical articles, minus the judgment (okay, maybe a little).</p>

<hr />

<h2 id="system-design--scalability--because-your-app-will-grow-whether-youre-ready-or-not"><strong>System Design &amp; Scalability — Because Your App <em>Will</em> Grow (Whether You’re Ready or Not)</strong></h2>

<p><strong>Summary:</strong>
This year, I spent a lot of time talking about <strong>scalable backend systems</strong> — not because every app needs to handle millions of users, but because <em>every app eventually meets its first “why is the server on fire?” moment</em>.</p>

<ul>
  <li><strong><a href="https://billyokeyo.dev/posts/building-scalable-backend-systems/">How to Build Scalable Backend Systems with Python, C#, PHP and Dart</a></strong>
A practical guide to designing systems that won’t collapse the moment your app gets featured on Twitter… or worse, WhatsApp groups.</li>
</ul>

<p><strong>Key lesson:</strong>
Scalability isn’t about overengineering — it’s about <em>not regretting your life choices later</em>.</p>

<hr />

<h2 id="testing--trust-issues-but-make-them-automated"><strong>Testing — Trust Issues, But Make Them Automated</strong></h2>

<p><strong>Summary:</strong>
Testing dominated a good part of the year, mainly because nothing builds trust issues faster than code that <em>looks correct</em> but isn’t.</p>

<p>This series walked through testing from the basics to full CI/CD integration — because “it works on my machine” is not a deployment strategy.</p>

<ul>
  <li><strong><a href="https://billyokeyo.dev/posts/unit-feature-integration-tests/">Unit, Integration, and End-to-End Tests — Part 1</a></strong>
Where we learn that tests are not the enemy — flaky tests are.</li>
  <li><strong><a href="https://billyokeyo.dev/posts/unit-feature-integration-tests/">Unit Testing in Depth — Part 2</a></strong>
Small tests, big confidence.</li>
  <li><strong><a href="https://billyokeyo.dev/posts/integration-testing/">Integration Testing in Depth — Part 3</a></strong>
When components finally talk to each other… and start arguing.</li>
  <li><strong><a href="https://billyokeyo.dev/posts/end-to-end-testing/">End-to-End Testing in Depth — Part 4</a></strong>
Testing your app the same way users break it.</li>
  <li><strong><a href="https://billyokeyo.dev/posts/testing-pyramid/">The Testing Pyramid &amp; CI/CD — Part 5</a></strong>
The moment you realize automation saves both time <em>and</em> sanity.</li>
</ul>

<p><strong>Key lesson:</strong>
Tests don’t slow you down — debugging production issues does.</p>

<hr />

<h2 id="performance--tooling--making-code-faster-without-sacrificing-sleep"><strong>Performance &amp; Tooling — Making Code Faster Without Sacrificing Sleep</strong></h2>

<p><strong>Summary:</strong>
Performance optimization showed up in different forms this year — from modern runtimes to build optimizations. Because sometimes the app isn’t slow… it’s just doing unnecessary work very enthusiastically.</p>

<ul>
  <li><strong><a href="https://billyokeyo.dev/posts/webassembly/">Diving into WebAssembly: What It Is and Why It Matters</a></strong>
For when JavaScript alone just isn’t fast enough.</li>
  <li><strong><a href="https://billyokeyo.dev/posts/tree-shaking-in-typescript/">Tree Shaking in TypeScript</a></strong>
Removing code you forgot existed but was still shipped to production.</li>
</ul>

<p><strong>Key lesson:</strong>
If users say your app is slow, they’re probably right. The logs just haven’t confessed yet.</p>

<hr />

<h2 id="engineering-culture--because-code-is-written-by-humans-flawed-ones"><strong>Engineering Culture — Because Code Is Written by Humans (Flawed Ones)</strong></h2>

<p><strong>Summary:</strong>
Not everything this year was serious architecture talk. Some articles leaned into the <em>very human side</em> of software development — where excuses are plentiful and accountability is… negotiable.</p>

<ul>
  <li><strong><a href="https://billyokeyo.dev/posts/developer-excuses-code-breaks/">Top 10 Developer Excuses When Code Breaks</a></strong>
A humorous breakdown of things we’ve all said — and what actually went wrong.</li>
</ul>

<p><strong>Key lesson:</strong>
If you’ve never blamed the cache, the network, or “some weird edge case,” are you even a developer?</p>

<hr />

<h2 id="industry-case-studies--learning-the-hard-way-so-you-dont-have-to"><strong>Industry Case Studies — Learning the Hard Way (So You Don’t Have To)</strong></h2>

<p><strong>Summary:</strong>
One of the most impactful pieces this year was breaking down a <strong>real-world outage</strong> — because nothing teaches engineering humility like watching large systems fail in creative ways.</p>

<ul>
  <li><strong><a href="https://billyokeyo.dev/posts/cloudflare-outage/">What Engineers Can Learn From the Cloudflare Outage</a></strong>
A reminder that one bad configuration can humble the biggest companies.</li>
</ul>

<p><strong>Key lesson:</strong>
Distributed systems don’t fail loudly — they fail <em>politely, globally, and at the worst possible time</em>.</p>

<hr />

<h2 id="what-2025-really-taught-me"><strong>What 2025 Really Taught Me</strong></h2>

<p>If I had to summarize the year in engineering truths:</p>

<ul>
  <li>Scalability matters <em>before</em> users complain.</li>
  <li>Tests are cheaper than apologies.</li>
  <li>Performance issues hide in plain sight.</li>
  <li>Systems fail — preparation determines whether you panic or recover.</li>
  <li>Developers are predictable creatures with unpredictable bugs.</li>
</ul>

<hr />

<h2 id="looking-ahead"><strong>Looking Ahead</strong></h2>

<p>In the next year, expect:</p>

<ul>
  <li>More real-world case studies</li>
  <li>Deeper dives into distributed systems and cloud patterns</li>
  <li>Practical articles you can actually apply on Monday morning</li>
</ul>

<p>Thank you for reading, sharing, and occasionally finding bugs in my examples (yes, I see you).</p>

<p>Here’s to another year of writing code, fixing mistakes, and pretending we knew what we were doing all along.</p>

]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/2025-review.png" medium="image" />
  
  
    
      <category>Engineering</category>
    
      <category>Year in Review</category>
    
  
  
    
      <category>Year in Review</category>
    
      <category>Engineering</category>
    
      <category>System Design</category>
    
      <category>Testing</category>
    
      <category>Performance</category>
    
      <category>Culture</category>
    
      <category>Case Studies</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>What Engineers Can Learn From the Cloudflare Outage (November 2025)</title>
      <link>https://billyokeyo.dev/posts/cloudflare-outage/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/cloudflare-outage/</guid>
      <pubDate>Fri, 21 Nov 2025 00:00:00 +0000</pubDate>
  
      <atom:updated>2025-11-21T09:01:11+00:00</atom:updated>
  
      <description><![CDATA[Learn the key takeaways from the Cloudflare outage (November 2025), including the importance of configuration management, the dangers of hidden assumptions, and best practices for incident response.]]></description>
      <content:encoded><![CDATA[<p><em>How a single oversized configuration file brought down parts of the internet and why this matters for every engineering team.</em></p>

<p>On November 18, 2025, the internet shook a little.</p>

<p>Cloudflare, the massive networking and security platform powering millions of websites globally, experienced a major outage that resulted in widespread 5xx errors across the internet. For several hours, key services like CDN routing, Workers KV, Access authentication, and even Cloudflare’s own dashboard were degraded.</p>

<p>In their official <a href="https://blog.cloudflare.com/18-november-2025-outage/" target="_blank"> <strong>incident report</strong>, </a> Cloudflare broke down exactly what happened, and the root cause was surprisingly small and deceptively simple:</p>

<blockquote>
  <p><strong>A configuration file grew larger than expected, violated a hidden assumption in the proxy code, and triggered runtime panics across the global edge.</strong></p>
</blockquote>

<p>This incident is a powerful case study in modern distributed systems. Let’s break down what went wrong — and why engineers everywhere should take note.</p>

<h2 id="what-actually-happened-a-chain-reaction-from-one-oversized-file"><strong>What Actually Happened? A Chain Reaction From One Oversized File</strong></h2>

<p>The root cause began with a permissions change in Cloudflare’s <strong>ClickHouse database cluster</strong>, which led to duplicate rows in a dataset used by their Bot Management engine. That duplication caused the generated “feature file”, a config-like file that proxies rely on to double in size.</p>

<p>Here’s where assumptions came back to haunt them:</p>

<ul>
  <li>Cloudflare’s proxy engine expected a maximum of <strong>200 features</strong>.</li>
  <li>The new file exceeded that limit.</li>
  <li>An <code>.unwrap()</code> in their Rust-based FL2 proxy assumed the file would always be valid.</li>
  <li>That assumption failed — the code panicked — resulting in cascading 5xx failures.</li>
</ul>

<p>As Cloudflare noted in their report, this caused two types of breakage across their edge:</p>

<ol>
  <li><strong>FL2 proxies (new engine)</strong>: Panicked → produced 5xx errors</li>
  <li><strong>FL proxies (old engine)</strong>: Failed to process bot scores → defaulted to zero → broke logic in Access, rules, authentication, and security products</li>
</ol>

<p>To make things even more confusing, Cloudflare’s status page (hosted externally) briefly went offline too, creating a misleading early hypothesis that the outage was a <strong>massive DDoS attack</strong>.</p>

<p>It wasn’t.
It was configuration drift.</p>

<h2 id="why-this-seemingly-small-bug-became-a-big-internet-event"><strong>Why This Seemingly Small Bug Became a Big Internet Event</strong></h2>

<p>Config files have become “just another part of the deployment pipeline,” especially in cloud platforms where machine-generated metadata drives features. But Cloudflare’s outage shows:</p>

<ul>
  <li><strong>Config is not static</strong></li>
  <li><strong>Config can be corrupted</strong></li>
  <li><strong>Config needs validation just like code</strong></li>
</ul>

<p>Because this file was distributed globally across tens of thousands of Cloudflare servers, a single flawed generation step caused a worldwide issue within minutes.</p>

<p>Distributed systems amplify mistakes — both good ones and bad ones.</p>

<h2 id="key-engineering-lessons-we-should-all-learn"><strong>Key Engineering Lessons We Should All Learn</strong></h2>

<h3 id="1-never-rely-on-hidden-assumptions"><strong>1. Never rely on hidden assumptions</strong></h3>

<p>Cloudflare’s proxy code assumed the feature file would never exceed a certain size. That “should never happen” moment is often the birthplace of outages.</p>

<p><strong>Lesson:</strong>
Add explicit limits, schema checks, and sanity validations to <em>all</em> config ingestion paths.</p>

<hr />

<h3 id="2-configuration-is-part-of-your-software-supply-chain"><strong>2. Configuration is part of your software supply chain</strong></h3>

<p>The feature file was generated, replicated, and consumed automatically — no human intervention. That makes it just as risky as code.</p>

<p><strong>Lesson:</strong>
Treat configuration pipelines as first-class citizens: test them, validate them, gate them.</p>

<h3 id="3-build-for-graceful-degradation-not-hard-crashes"><strong>3. Build for graceful degradation, not hard crashes</strong></h3>

<p>A single <code>.unwrap()</code> took down parts of the internet.</p>

<p><strong>Lesson:</strong>
Fail softly.
If config is invalid, degrade safely, skip rules, or revert to defaults — don’t panic.</p>

<h3 id="4-feature-flags-and-kill-switches-are-essential"><strong>4. Feature flags and kill switches are essential</strong></h3>

<p>Cloudflare themselves acknowledged the need for a more robust kill-switch system in their follow-up plans.</p>

<p><strong>Lesson:</strong>
Every modern engineering team should have:</p>

<ul>
  <li>global feature kill switches</li>
  <li>fast configuration rollback</li>
  <li>manual override options</li>
</ul>

<h3 id="5-monitoring-needs-context-not-just-alarms"><strong>5. Monitoring needs context, not just alarms</strong></h3>

<p>Many engineers watching the outage thought it was an external attack. Alerting didn’t tell them <strong>where</strong> the failure was coming from, just that everything was “down.”</p>

<p><strong>Lesson:</strong>
Monitoring should distinguish between:</p>

<ul>
  <li>origin failure</li>
  <li>edge failure</li>
  <li>config failure</li>
  <li>auth failure</li>
  <li>internal propagation issues</li>
</ul>

<p>Context reduces misdiagnosis.</p>

<h3 id="6-observability-tools-must-be-lightweight-during-crises"><strong>6. Observability tools must be lightweight during crises</strong></h3>

<p>During recovery, Cloudflare reported that their debugging systems started consuming high CPU, which slowed other mission-critical services.</p>

<p><strong>Lesson:</strong>
Your troubleshooting tools <strong>must</strong> use minimal resources when the system is stressed.</p>

<h3 id="7-transparency-helps-the-whole-industry-learn"><strong>7. Transparency helps the whole industry learn</strong></h3>

<p>Cloudflare’s detailed post-mortem is a model for engineering culture. Their openness helps engineers worldwide understand real failure modes in large-scale systems.</p>

<p><strong>Lesson:</strong>
Share your failures.
They help others avoid the same mistakes.</p>

<hr />

<h2 id="the-bigger-picture-why-this-incident-matters"><strong>The Bigger Picture: Why This Incident Matters</strong></h2>

<p>Cloudflare’s outage wasn’t just a story about a config file. It was a reminder of how fragile the modern internet can be:</p>

<ul>
  <li>We automate everything</li>
  <li>We trust our pipelines</li>
  <li>We deploy continuously</li>
  <li>We assume schemas won’t change</li>
  <li>We rely on millions of machines making the same decision correctly</li>
</ul>

<p>But when the assumptions underneath those systems break, failures propagate at machine speed.</p>

<p>For engineers, SREs, DevOps teams, and platform architects, this incident underscores a fundamental truth:</p>

<blockquote>
  <p><strong>Your system is only as resilient as your least-validated assumption.</strong></p>
</blockquote>

<h2 id="final-thoughts"><strong>Final Thoughts</strong></h2>

<p>Cloudflare’s outage was a blip in the timeline of the internet, but the lessons are timeless.</p>

<p>Distributed systems will always fail, the question is how gracefully they fail, how quickly they recover, and how deeply the organization learns from the event.</p>

<p>Cloudflare’s transparency, analysis, and remediation steps set a strong example for engineering teams everywhere.</p>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/cloudflare-outage.jpg" medium="image" />
  
  
    
      <category>Engineering</category>
    
      <category>DevOps</category>
    
      <category>SRE</category>
    
      <category>Cloudflare</category>
    
      <category>Outages</category>
    
      <category>Distributed Systems</category>
    
  
  
    
      <category>Cloudflare</category>
    
      <category>Outages</category>
    
      <category>Distributed Systems</category>
    
      <category>Incident Response</category>
    
      <category>Engineering Best Practices</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Stripe Connect Integration Guide — Standard, Express, and Custom Accounts Explained (with Laravel, C#, and Python Examples)</title>
      <link>https://billyokeyo.dev/posts/integrating-to-stripe/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/integrating-to-stripe/</guid>
      <pubDate>Tue, 21 Oct 2025 00:00:00 +0000</pubDate>
  
      <atom:updated>2026-04-22T09:22:08+00:00</atom:updated>
  
      <description><![CDATA[Learn the differences between Stripe Connect's Standard, Express, and Custom account types. This comprehensive guide covers when to use each type, implementation examples in Laravel, C#, and Python, and key architectural considerations for building payment platforms.]]></description>
      <content:encoded><![CDATA[<blockquote>
  <p><strong>Update (2026):</strong> Stripe’s <strong><a href="https://docs.stripe.com/connect/accounts-v2">Accounts v2 API</a></strong> uses a unified <code>Account</code> with <strong>configurations</strong> (<code>merchant</code>, <code>customer</code>, <code>recipient</code>) so one identity can sell, pay your platform, and receive transfers without parallel <strong>Customer</strong> IDs. For a <strong>full guide</strong> in the same spirit as this article—but aligned to v2—see <strong><a href="https://billyokeyo.dev/posts/stripe-connect-accounts-v2/">Stripe Connect on Accounts v2</a></strong>. <strong>Everything below</strong> remains the reference for <strong>Accounts v1</strong> (<code>Account.create</code>, OAuth, Account Links as shown). V2 though is the recommended path for the new Stripe Connect Integrations.</p>
</blockquote>

<p>Stripe offers a powerful ecosystem for building <strong>payment platforms</strong>, <strong>marketplaces</strong>, and <strong>financial applications</strong>.
One of its most powerful products, <strong>Stripe Connect</strong>, allows you to facilitate payments between multiple parties while maintaining flexibility over <strong>branding, compliance,</strong> and <strong>user experience</strong>.</p>

<p>Choosing between <strong>Standard</strong>, <strong>Express</strong>, and <strong>Custom</strong> accounts is one of the most important architectural decisions you’ll make when designing a payment platform.
This guide combines a <strong>step-by-step tutorial</strong> and <strong>deep technical analysis</strong> to help you understand and implement each type.</p>

<h2 id="what-is-stripe-connect">What Is Stripe Connect?</h2>

<p>Stripe Connect is designed for <strong>platforms</strong> that process payments on behalf of others.
It enables you to connect user accounts (called <strong>Connected Accounts</strong>) to your <strong>Platform Account</strong> so you can:</p>

<ul>
  <li>Collect payments from customers</li>
  <li>Distribute payouts to vendors, lenders, or service providers</li>
  <li>Optionally collect platform fees</li>
</ul>

<p>Your <strong>platform never needs to hold funds directly</strong> (unless you design it to), and Stripe handles the movement of money under the hood.</p>

<h2 id="connected-accounts-overview">Connected Accounts Overview</h2>

<p>Each connected account represents a business, seller, or user on your platform.
Depending on how much <strong>control</strong> and <strong>responsibility</strong> you want, you’ll choose between:</p>

<ol>
  <li><strong>Standard</strong> — Stripe handles almost everything.</li>
  <li><strong>Express</strong> — Stripe handles compliance, you handle limited UX.</li>
  <li><strong>Custom</strong> — You handle everything; Stripe is invisible to your users.</li>
</ol>

<table>
  <thead>
    <tr>
      <th>Feature / Aspect</th>
      <th><strong>Standard</strong></th>
      <th><strong>Express</strong></th>
      <th><strong>Custom</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Ownership of Stripe Account</strong></td>
      <td>User owns and manages their Stripe account directly.</td>
      <td>User gets a lightweight managed account. Stripe handles UI for onboarding and dashboards.</td>
      <td>Full control; your platform owns the payment experience. Users never see Stripe.</td>
    </tr>
    <tr>
      <td><strong>KYC / Onboarding</strong></td>
      <td>Handled entirely by Stripe via the user’s Stripe dashboard.</td>
      <td>Stripe-hosted onboarding flow with minimal setup.</td>
      <td>You collect all KYC data through your own UI and pass it to Stripe’s API.</td>
    </tr>
    <tr>
      <td><strong>Dashboard Access</strong></td>
      <td>User uses Stripe’s own dashboard.</td>
      <td>Limited dashboard (Stripe Express Dashboard).</td>
      <td>No dashboard; your app must display balances, transactions, etc.</td>
    </tr>
    <tr>
      <td><strong>Compliance Responsibility</strong></td>
      <td>Stripe handles everything.</td>
      <td>Stripe handles most KYC and compliance.</td>
      <td>You are responsible for collecting and transmitting KYC data.</td>
    </tr>
    <tr>
      <td><strong>Control over UI/UX</strong></td>
      <td>Minimal; users leave your app to manage payments.</td>
      <td>Moderate; Stripe provides branded but embeddable flows.</td>
      <td>Full; completely white-labeled experience.</td>
    </tr>
    <tr>
      <td><strong>Ideal Use Case</strong></td>
      <td>Marketplaces where users already have Stripe accounts.</td>
      <td>Platforms needing simple onboarding (e.g., gig apps).</td>
      <td>Fintech, lending, or platforms needing deep financial workflows.</td>
    </tr>
  </tbody>
</table>

<h2 id="architectural-overview">Architectural Overview</h2>

<p>All three integration types share a common flow:</p>

<pre><code>Customer → Your Platform → Connected Account → Bank Payout
</code></pre>

<p>However, <strong>the control points differ</strong>:</p>

<ul>
  <li>With <strong>Standard</strong>, Stripe owns the UX and dashboard.</li>
  <li>With <strong>Express</strong>, Stripe hosts onboarding and dashboard, but your platform manages relationships.</li>
  <li>With <strong>Custom</strong>, your platform builds everything: UI, onboarding, payouts, and reporting.</li>
</ul>

<h2 id="step-by-step-implementation-by-account-type">Step-by-Step Implementation by Account Type</h2>

<p>Let’s look at how to implement each account type with <strong>C#</strong>, <strong>Laravel (PHP)</strong> and <strong>Python</strong> examples and understand their implications.</p>

<h2 id="standard-accounts--easiest-setup-minimal-control">Standard Accounts — Easiest Setup, Minimal Control</h2>

<h3 id="concept">Concept</h3>

<p>Standard accounts are the simplest to integrate.
Users connect <strong>their own existing Stripe accounts</strong> using OAuth. Stripe handles compliance, payouts, and reporting. Your platform receives a small <strong>application fee</strong> from each transaction.</p>

<h3 id="use-case">Use Case</h3>

<blockquote>
  <p>Ideal for marketplaces or SaaS platforms where users already have Stripe accounts and prefer to manage their own dashboards.</p>
</blockquote>

<h3 id="implementation">Implementation</h3>

<h4 id="c">C#</h4>

<pre><code class="language-csharp">var accountLink = await stripe.AccountLinks.CreateAsync(new AccountLinkCreateOptions {
    Account = connectedAccountId,
    RefreshUrl = "https://your-platform.com/reauth",
    ReturnUrl = "https://your-platform.com/success",
    Type = "account_onboarding",
});
</code></pre>

<h4 id="laravel-php">Laravel (PHP)</h4>

<pre><code class="language-php">$accountLink = \Stripe\AccountLink::create([
    'account' =&gt; $connectedAccountId,
    'refresh_url' =&gt; route('stripe.reauth'),
    'return_url' =&gt; route('stripe.success'),
    'type' =&gt; 'account_onboarding',
]);
</code></pre>

<h4 id="python">Python</h4>

<pre><code class="language-python">import stripe
stripe.api_key = "sk_test_..."

account_link = stripe.AccountLink.create(
    account=connected_account_id,
    refresh_url="https://your-platform.com/reauth",
    return_url="https://your-platform.com/success",
    type="account_onboarding"
)
</code></pre>

<h3 id="pros-and-cons">Pros and Cons</h3>

<p><strong>Pros</strong></p>

<ul>
  <li>Minimal setup</li>
  <li>Stripe handles everything (KYC, compliance, payouts)</li>
  <li>Reduced liability</li>
</ul>

<p><strong>Cons</strong></p>

<ul>
  <li>Limited branding control</li>
  <li>Users must leave your platform to manage payments</li>
  <li>Harder to create a unified experience</li>
</ul>

<h2 id="express-accounts--fast-onboarding-shared-control">Express Accounts — Fast Onboarding, Shared Control</h2>

<h3 id="concept-1">Concept</h3>

<p>Express accounts strike a balance between simplicity and control.
You manage account creation and linking, but Stripe handles onboarding and provides a <strong>lightweight dashboard</strong> for your users.</p>

<h3 id="use-case-1">Use Case</h3>

<blockquote>
  <p>Perfect for gig or service platforms (like driver or freelancer apps) where quick onboarding and basic payout visibility are key.</p>
</blockquote>

<h3 id="implementation-1">Implementation</h3>

<h4 id="c-1">C#</h4>

<pre><code class="language-csharp">var account = await stripe.Accounts.CreateAsync(new AccountCreateOptions {
    Type = "express",
    Country = "US",
    Email = "user@example.com"
});

var link = await stripe.AccountLinks.CreateAsync(new AccountLinkCreateOptions {
    Account = account.Id,
    RefreshUrl = "https://yourapp.com/reauth",
    ReturnUrl = "https://yourapp.com/complete",
    Type = "account_onboarding"
});
</code></pre>

<h4 id="laravel-php-1">Laravel (PHP)</h4>

<pre><code class="language-php">$account = \Stripe\Account::create([
    'type' =&gt; 'express',
    'country' =&gt; 'US',
    'email' =&gt; $request-&gt;input('email'),
]);

$link = \Stripe\AccountLink::create([
    'account' =&gt; $account-&gt;id,
    'refresh_url' =&gt; route('stripe.reauth'),
    'return_url' =&gt; route('stripe.success'),
    'type' =&gt; 'account_onboarding',
]);
</code></pre>

<h4 id="python-1">Python</h4>

<pre><code class="language-python">account = stripe.Account.create(
    type="express",
    country="US",
    email="user@example.com"
)

link = stripe.AccountLink.create(
    account=account.id,
    refresh_url="https://yourapp.com/reauth",
    return_url="https://yourapp.com/complete",
    type="account_onboarding"
)
</code></pre>

<h3 id="pros-and-cons-1">Pros and Cons</h3>

<p><strong>Pros</strong></p>

<ul>
  <li>Faster onboarding</li>
  <li>Stripe handles compliance and verification</li>
  <li>Users get access to payout history via the Express dashboard</li>
</ul>

<p><strong>Cons</strong></p>

<ul>
  <li>Limited customization</li>
  <li>Stripe branding remains visible</li>
  <li>Less flexibility over reporting or custom payout logic</li>
</ul>

<h2 id="custom-accounts--full-control-full-responsibility">Custom Accounts — Full Control, Full Responsibility</h2>

<h3 id="concept-2">Concept</h3>

<p>Custom accounts are designed for <strong>white-labeled</strong> platforms.
Your app controls <strong>everything</strong>: onboarding, KYC collection, balances, and payouts.
Stripe is completely invisible to the end user.</p>

<h3 id="use-case-2">Use Case</h3>

<blockquote>
  <p>Ideal for fintech, embedded finance, lending, or any system that requires deep integration and custom user experiences.</p>
</blockquote>

<h3 id="implementation-2">Implementation</h3>

<h4 id="c-2">C#</h4>

<pre><code class="language-csharp">var accountOptions = new AccountCreateOptions {
    Type = "custom",
    Country = "US",
    Email = data.Email.ToLower(),
    Capabilities = new AccountCapabilitiesOptions {
        CardPayments = new AccountCapabilitiesCardPaymentsOptions { Requested = true },
        Transfers = new AccountCapabilitiesTransfersOptions { Requested = true }
    },
    TosAcceptance = new AccountTosAcceptanceOptions {
        Date = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
        Ip = httpContext.Connection.RemoteIpAddress.ToString()
    }
};

var account = await stripe.Accounts.CreateAsync(accountOptions);
</code></pre>

<h4 id="laravel-php-2">Laravel (PHP)</h4>

<pre><code class="language-php">$account = \Stripe\Account::create([
    'type' =&gt; 'custom',
    'country' =&gt; 'US',
    'email' =&gt; strtolower($data['email']),
    'capabilities' =&gt; [
        'card_payments' =&gt; ['requested' =&gt; true],
        'transfers' =&gt; ['requested' =&gt; true],
    ],
    'tos_acceptance' =&gt; [
        'date' =&gt; time(),
        'ip' =&gt; request()-&gt;ip(),
    ],
]);
</code></pre>

<h4 id="python-2">Python</h4>

<pre><code class="language-python">account = stripe.Account.create(
    type="custom",
    country="US",
    email=data["email"].lower(),
    capabilities={
        "card_payments": {"requested": True},
        "transfers": {"requested": True},
    },
    tos_acceptance={
        "date": int(time.time()),
        "ip": request.remote_addr,
    }
)
</code></pre>

<h3 id="fund-flow-example">Fund Flow Example</h3>

<pre><code>+--------------------------------------+
|             Platform (You)           |
|    No direct money handling          |
+--------------------+-----------------+
                     |
                     ▼
          Create &amp; Manage Connected Accounts
                     |
   +--------------------------------------+
   |     Connected Account (Business)     |
   |  💵 Has Stripe balance, bank info    |
   |  🧾 Disburses and collects funds     |
   +--------------------------------------+
</code></pre>

<p><strong>Money Movement</strong></p>

<ul>
  <li><strong>IN:</strong> Customer → Connected Account (via ACH/Card)</li>
  <li><strong>OUT:</strong> Connected Account → Bank (payouts)</li>
  <li>Platform orchestrates, Stripe moves the money</li>
</ul>

<hr />

<h2 id="checking-balances">Checking Balances</h2>

<h4 id="python-3">Python</h4>

<pre><code class="language-python">balance = stripe.Balance.retrieve(stripe_account=account_id)
</code></pre>

<h4 id="laravel-php-3">Laravel (PHP)</h4>

<pre><code class="language-php">$balance = \Stripe\Balance::retrieve([], ['stripe_account' =&gt; $accountId]);
</code></pre>

<h4 id="c-3">C#</h4>

<pre><code class="language-csharp">var balance = await stripe.Balance.GetAsync(
    new BalanceGetOptions(),
    new RequestOptions { StripeAccount = accountId }
);
</code></pre>

<h2 id="compliance-responsibilities">Compliance Responsibilities</h2>

<table>
  <thead>
    <tr>
      <th>Responsibility</th>
      <th>Standard</th>
      <th>Express</th>
      <th>Custom</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>KYC</td>
      <td>Stripe</td>
      <td>Stripe</td>
      <td>You</td>
    </tr>
    <tr>
      <td>Tax Reporting</td>
      <td>Stripe</td>
      <td>Stripe</td>
      <td>You</td>
    </tr>
    <tr>
      <td>PCI Compliance</td>
      <td>Stripe-hosted</td>
      <td>Shared</td>
      <td>Mostly you</td>
    </tr>
    <tr>
      <td>Dispute Handling</td>
      <td>Stripe</td>
      <td>Shared</td>
      <td>You</td>
    </tr>
    <tr>
      <td>Branding</td>
      <td>Stripe</td>
      <td>Partial</td>
      <td>Fully yours</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p><strong>Tip:</strong> For Custom accounts, implement <strong>Stripe Identity</strong>, <strong>webhooks</strong>, and <strong>Stripe Radar</strong> to automate verification and fraud detection.</p>
</blockquote>

<h2 id="choosing-the-right-integration-type">Choosing the Right Integration Type</h2>

<table>
  <thead>
    <tr>
      <th>Scenario</th>
      <th>Recommended Type</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Marketplace with existing Stripe users</td>
      <td><strong>Standard</strong></td>
    </tr>
    <tr>
      <td>Platform needing fast onboarding</td>
      <td><strong>Express</strong></td>
    </tr>
    <tr>
      <td>Fintech, lending, or white-labeled finance</td>
      <td><strong>Custom</strong></td>
    </tr>
  </tbody>
</table>

<h2 id="strategic-considerations">Strategic Considerations</h2>

<ul>
  <li><strong>Time-to-market:</strong> Standard &lt; Express &lt; Custom</li>
  <li><strong>Compliance load:</strong> Stripe-heavy → Custom-heavy</li>
  <li><strong>Branding control:</strong> Minimal → Full</li>
  <li><strong>Revenue potential:</strong> Low (Standard) → High (Custom)</li>
</ul>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>Every Stripe Connect integration represents a trade-off between <strong>control</strong>, <strong>compliance</strong>, and <strong>complexity</strong>:</p>

<ul>
  <li><strong>Standard</strong> — easiest to deploy, lowest control</li>
  <li><strong>Express</strong> — balanced control and simplicity</li>
  <li><strong>Custom</strong> — ultimate flexibility with greater responsibility</li>
</ul>

<p>If your goal is <strong>speed</strong>, start with Express.
If your goal is <strong>brand control and scalability</strong>, build with Custom.</p>

<p>Either way, Stripe Connect gives you a future-proof foundation for managing payments, onboarding users, and creating rich financial experiences all through powerful APIs available across languages.</p>

]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/stripe-integration.jpg" medium="image" />
  
  
    
      <category>Payment Processing</category>
    
      <category>Software Development</category>
    
  
  
    
      <category>Stripe</category>
    
      <category>Payment Processing</category>
    
      <category>Software Development</category>
    
      <category>APIs</category>
    
      <category>Fintech</category>
    
      <category>Integrating Stripe Laravel</category>
    
      <category>Integrating Stripe C#</category>
    
      <category>Integrating Stripe Python</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>The Testing Pyramid: Wrapping Up with CI/CD and Best Practices - Part 5</title>
      <link>https://billyokeyo.dev/posts/testing-pyramid/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/testing-pyramid/</guid>
      <pubDate>Fri, 19 Sep 2025 00:00:00 +0000</pubDate>
  
      <atom:updated>2025-09-19T20:35:49+00:00</atom:updated>
  
      <description><![CDATA[The Testing Pyramid is a crucial model for structuring your automated tests effectively. This guide explores how to balance unit, integration, and end-to-end tests, integrate them into CI/CD pipelines, and follow best practices for a sustainable testing strategy.]]></description>
      <content:encoded><![CDATA[<p>Software testing is more than writing a few unit tests and hoping for the best. To deliver reliable software, teams need a <strong>balanced testing strategy</strong> — one that combines <strong>Unit, Integration, and End-to-End (E2E) tests</strong> in the right proportions. This is where the <strong>Testing Pyramid</strong> comes in.</p>

<p>In this final part of our testing series, we’ll:</p>

<ul>
  <li>Understand the <strong>Testing Pyramid model</strong>.</li>
  <li>Explore how to <strong>balance different types of tests</strong>.</li>
  <li>Learn how to <strong>integrate testing into CI/CD pipelines</strong>.</li>
  <li>Review <strong>best practices</strong> for building a sustainable testing culture.</li>
</ul>

<h2 id="the-testing-pyramid-explained">The Testing Pyramid Explained</h2>

<p>The <strong>Testing Pyramid</strong> (popularized by Mike Cohn) is a metaphor for structuring automated tests:</p>

<pre><code>        ▲
        |   End-to-End (fewest, slowest)
        |
        |   Integration (moderate amount, balanced)
        |
        |   Unit Tests (largest base, fastest)
        ▼
</code></pre>

<h3 id="1-unit-tests-foundation">1. Unit Tests (Foundation)</h3>

<ul>
  <li><strong>Fast, cheap, and precise</strong>.</li>
  <li>Cover small pieces of logic in isolation.</li>
  <li>Should make up <strong>60–70%</strong> of your automated test suite.</li>
</ul>

<blockquote>
  <p>Example: Testing a function that calculates interest rates.</p>
</blockquote>

<h3 id="2-integration-tests-middle-layer">2. Integration Tests (Middle Layer)</h3>

<ul>
  <li><strong>Validate how components interact</strong>.</li>
  <li>Cover database queries, API requests, or service orchestration.</li>
  <li>Should make up <strong>20–30%</strong> of your test suite.</li>
</ul>

<blockquote>
  <p>Example: Ensuring your API endpoint correctly fetches data from the database and formats the response.</p>
</blockquote>

<h3 id="3-end-to-end-tests-top">3. End-to-End Tests (Top)</h3>

<ul>
  <li><strong>Simulate real user flows</strong> across the full stack.</li>
  <li>Catch bugs unit or integration tests can’t.</li>
  <li>Should make up <strong>10–15%</strong> of your suite (because they’re slow and expensive).</li>
</ul>

<blockquote>
  <p>Example: A test where a user logs in, adds a product to their cart, and completes checkout.</p>
</blockquote>

<h2 id="integrating-tests-into-cicd-pipelines">Integrating Tests into CI/CD Pipelines</h2>

<p>Automated tests are only valuable if they’re <strong>run consistently</strong>. Modern software teams embed them into CI/CD pipelines.</p>

<h3 id="step-1-run-unit-tests-early">Step 1: Run Unit Tests Early</h3>

<ul>
  <li>Execute on <strong>every commit or pull request</strong>.</li>
  <li>Fail fast: block merges if unit tests fail.</li>
</ul>

<h3 id="step-2-run-integration-tests-on-builddeploy">Step 2: Run Integration Tests on Build/Deploy</h3>

<ul>
  <li>Run after the app compiles successfully.</li>
  <li>Can use a <strong>containerized test environment</strong> with mock or staging databases.</li>
</ul>

<h3 id="step-3-run-end-to-end-tests-on-staging">Step 3: Run End-to-End Tests on Staging</h3>

<ul>
  <li>Triggered before production deployment.</li>
  <li>Some teams run <strong>smoke E2E tests</strong> in production (carefully) to ensure critical flows still work.</li>
</ul>

<blockquote>
  <p>Example CI/CD Flow (GitHub Actions / GitLab / Jenkins):</p>
</blockquote>

<pre><code class="language-yaml">jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install dependencies
        run: npm install
      - name: Run unit tests
        run: npm test -- --unit
      - name: Run integration tests
        run: npm test -- --integration
      - name: Run e2e tests
        run: npm run test:e2e
</code></pre>

<h2 id="best-practices-for-a-balanced-testing-strategy">Best Practices for a Balanced Testing Strategy</h2>

<h3 id="1-follow-the-pyramid-not-the-ice-cream-cone">1. Follow the Pyramid, Not the Ice Cream Cone</h3>

<ul>
  <li>Too many E2E tests = <strong>slow, brittle pipeline</strong>.</li>
  <li>Too few unit tests = <strong>shaky foundation</strong>.</li>
  <li>Balance is key.</li>
</ul>

<h3 id="2-use-test-doubles-wisely">2. Use Test Doubles Wisely</h3>

<ul>
  <li><strong>Mocks/Stubs</strong> in unit tests to isolate dependencies.</li>
  <li><strong>Minimal mocking</strong> in integration tests — rely on real services where possible.</li>
</ul>

<h3 id="3-make-tests-deterministic">3. Make Tests Deterministic</h3>

<ul>
  <li>Tests should <strong>always produce the same result</strong>.</li>
  <li>Avoid flaky tests (caused by race conditions, timeouts, or external dependencies).</li>
</ul>

<h3 id="4-keep-tests-fast">4. Keep Tests Fast</h3>

<ul>
  <li>Aim for <strong>seconds, not minutes</strong>.</li>
  <li>Developers won’t run slow tests locally.</li>
</ul>

<h3 id="5-automate-everything-in-cicd">5. Automate Everything in CI/CD</h3>

<ul>
  <li>Manual testing has its place, but regression checks must be automated.</li>
  <li>CI/CD pipelines should be your <strong>safety net</strong> before production.</li>
</ul>

<h3 id="6-monitor-and-improve-test-coverage">6. Monitor and Improve Test Coverage</h3>

<ul>
  <li>Coverage isn’t everything, but low coverage usually indicates gaps.</li>
  <li>Focus on <strong>critical paths</strong> rather than chasing 100%.</li>
</ul>

<h3 id="7-treat-tests-as-code">7. Treat Tests as Code</h3>

<ul>
  <li>Tests should be <strong>maintainable, reviewed, and refactored</strong> like production code.</li>
  <li>Avoid “test rot” where tests become outdated or ignored.</li>
</ul>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>The journey from <strong>unit → integration → E2E</strong> tests gives you <strong>confidence</strong> at every level of your system.</p>

<ul>
  <li><strong>Unit tests</strong> keep your building blocks solid.</li>
  <li><strong>Integration tests</strong> ensure the blocks fit together.</li>
  <li><strong>E2E tests</strong> confirm the entire structure works as intended.</li>
</ul>

<p>By following the <strong>Testing Pyramid</strong>, integrating tests into <strong>CI/CD pipelines</strong>, and practicing discipline in writing effective tests, you’ll achieve:</p>

<ul>
  <li>Faster feedback loops.</li>
  <li>Fewer production bugs.</li>
  <li>Higher developer confidence.</li>
  <li>Happier end-users.</li>
</ul>

<p>Testing isn’t just about catching bugs, it’s about building <strong>trustworthy software</strong> at scale.</p>

<p><strong>Next Steps for You:</strong></p>

<ul>
  <li>Audit your current test suite.</li>
  <li>See if you’re over-relying on one type of test.</li>
  <li>Gradually reshape your suite into a <strong>pyramid</strong>, not an ice cream cone.</li>
</ul>

]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/software-testing.webp" medium="image" />
  
  
    
      <category>Testing</category>
    
      <category>Software Development</category>
    
  
  
    
      <category>Testing</category>
    
      <category>Software Development</category>
    
      <category>Quality Assurance</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>End-to-End (E2E) Testing in Depth - Part 4</title>
      <link>https://billyokeyo.dev/posts/e2e-testing/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/e2e-testing/</guid>
      <pubDate>Fri, 12 Sep 2025 00:00:00 +0000</pubDate>
  
      <atom:updated>2025-09-12T00:00:00+00:00</atom:updated>
  
      <description><![CDATA[E2E tests validate user workflows from start to finish, ensuring the entire application stack works as expected. They cover everything from frontend to backend, database, APIs, and sometimes even external services.]]></description>
      <content:encoded><![CDATA[<p>If <strong>unit tests</strong> check individual functions, and <strong>integration tests</strong> check how those functions work together, <strong>end-to-end (E2E) tests</strong> take it all the way:
They simulate <strong>real user workflows</strong> from start to finish, ensuring the entire application stack works as expected.</p>

<p>E2E testing is about validating the <strong>system as a whole</strong>, covering everything from frontend to backend, database, APIs, and sometimes even external services.</p>

<h2 id="what-are-e2e-tests">What Are E2E Tests?</h2>

<p>E2E tests replicate <strong>user behavior</strong> and verify that the system responds correctly. Think of them as automated users interacting with your app.</p>

<p>Example workflows tested in E2E:</p>

<ul>
  <li>User logs in → Dashboard loads correctly.</li>
  <li>User creates a record → It’s stored in DB → Appears in the UI.</li>
  <li>Checkout flow in an e-commerce app → Product added → Payment processed → Order confirmed.</li>
</ul>

<h2 id="why-e2e-tests-matter">Why E2E Tests Matter</h2>

<ul>
  <li><strong>Validate Real Workflows</strong> → Ensure the system behaves like users expect.</li>
  <li><strong>Catch System-Wide Failures</strong> → Config issues, routing errors, DB schema mismatches.</li>
  <li><strong>Test Critical Paths</strong> → Login, payments, onboarding, search, etc.</li>
  <li><strong>Provide Business Confidence</strong> → Stakeholders see features working as intended.</li>
</ul>

<h2 id="characteristics-of-e2e-tests">Characteristics of E2E Tests</h2>

<ul>
  <li><strong>High Coverage</strong> – Test across the full stack.</li>
  <li><strong>Slowest of All Tests</strong> – Often run in CI/CD pipelines, not during active coding.</li>
  <li><strong>Brittle</strong> – Small UI changes can break tests, so balance is key.</li>
  <li><strong>Mimic Real User Behavior</strong> – Often browser-driven or API-driven.</li>
</ul>

<h1 id="side-by-side-examples">Side-by-Side Examples</h1>

<p>Scenario:</p>

<blockquote>
  <p>A user visits a web app, logs in, and sees their profile page with their name.</p>
</blockquote>

<h3 id="python-selenium--pytest">Python (Selenium + pytest)</h3>

<pre><code class="language-python"># test_e2e_login.py
from selenium import webdriver
from selenium.webdriver.common.by import By
import pytest

@pytest.fixture
def driver():
    driver = webdriver.Chrome()
    yield driver
    driver.quit()

def test_login_flow(driver):
    driver.get("http://localhost:5000/login")

    driver.find_element(By.NAME, "username").send_keys("alice")
    driver.find_element(By.NAME, "password").send_keys("password123")
    driver.find_element(By.ID, "login-button").click()

    profile_name = driver.find_element(By.ID, "profile-name").text
    assert profile_name == "Alice"
</code></pre>

<h3 id="c-selenium--xunit">C# (Selenium + xUnit)</h3>

<pre><code class="language-csharp">using Xunit;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

public class LoginE2ETests
{
    [Fact]
    public void Login_ShowsProfilePage()
    {
        using var driver = new ChromeDriver();
        driver.Navigate().GoToUrl("http://localhost:5000/login");

        driver.FindElement(By.Name("username")).SendKeys("alice");
        driver.FindElement(By.Name("password")).SendKeys("password123");
        driver.FindElement(By.Id("login-button")).Click();

        var profileName = driver.FindElement(By.Id("profile-name")).Text;
        Assert.Equal("Alice", profileName);
    }
}
</code></pre>

<h3 id="typescript-playwright--cypress">TypeScript (Playwright / Cypress)</h3>

<p><em>(Playwright example below — Cypress would be very similar)</em></p>

<pre><code class="language-typescript">// login.e2e.test.ts
import { test, expect } from "@playwright/test";

test("user can login and see profile page", async ({ page }) =&gt; {
  await page.goto("http://localhost:3000/login");

  await page.fill("input[name=username]", "alice");
  await page.fill("input[name=password]", "password123");
  await page.click("#login-button");

  const profileName = await page.textContent("#profile-name");
  expect(profileName).toBe("Alice");
});
</code></pre>

<h3 id="php-laravel-dusk-for-browser-automation">PHP (Laravel Dusk for browser automation)</h3>

<pre><code class="language-php">// tests/Browser/LoginTest.php
&lt;?php

namespace Tests\Browser;

use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class LoginTest extends DuskTestCase
{
    public function testUserCanLoginAndSeeProfile()
    {
        $this-&gt;browse(function (Browser $browser) {
            $browser-&gt;visit('/login')
                    -&gt;type('username', 'alice')
                    -&gt;type('password', 'password123')
                    -&gt;press('Login')
                    -&gt;assertSee('Alice');
        });
    }
}
</code></pre>

<h1 id="comparison-table">Comparison Table</h1>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>E2E Testing Goal</th>
      <th>Python (Selenium)</th>
      <th>C# (Selenium + xUnit)</th>
      <th>TypeScript (Playwright/Cypress)</th>
      <th>PHP (Laravel Dusk)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Scope</strong></td>
      <td>Full user workflow (UI → backend → DB)</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td><strong>Tools</strong></td>
      <td>Selenium</td>
      <td>Selenium</td>
      <td>Playwright/Cypress</td>
      <td>Dusk (browser automation)</td>
      <td> </td>
    </tr>
    <tr>
      <td><strong>Speed</strong></td>
      <td>Slow (browser interaction)</td>
      <td>Slow</td>
      <td>Medium</td>
      <td>Medium/Fast</td>
      <td> </td>
    </tr>
    <tr>
      <td><strong>Confidence</strong></td>
      <td>Highest — validates the system end-to-end</td>
      <td>⭐⭐⭐</td>
      <td>⭐⭐⭐</td>
      <td>⭐⭐⭐</td>
      <td>⭐⭐⭐</td>
    </tr>
    <tr>
      <td><strong>Best For</strong></td>
      <td>Login flows, payments, user journeys</td>
      <td>UI + API flows</td>
      <td>UI + API flows</td>
      <td>Fullstack web apps</td>
      <td>Laravel apps</td>
    </tr>
  </tbody>
</table>

<h1 id="best-practices-for-writing-effective-e2e-tests">Best Practices for Writing Effective E2E Tests</h1>

<ol>
  <li>Test Critical User Journeys Only
    <ul>
      <li>Focus on login, checkout, payments, onboarding, etc.</li>
      <li>Don’t waste time E2E testing every small feature.</li>
    </ul>
  </li>
  <li>Keep Tests Deterministic
    <ul>
      <li>Avoid flakiness by controlling test data.</li>
      <li>Seed databases with known test records.</li>
    </ul>
  </li>
  <li>Use Test-Specific Environments
    <ul>
      <li>Run E2E tests in staging or CI environments.</li>
      <li>Isolate from production data and services.</li>
    </ul>
  </li>
  <li>Clean Up After Tests
    <ul>
      <li>Ensure created records (users, orders) are rolled back or deleted.</li>
    </ul>
  </li>
  <li>Use IDs and Stable Selectors
    <ul>
      <li>Avoid brittle selectors like div:nth-child(3).</li>
      <li>Use unique IDs or data-test attributes.</li>
    </ul>
  </li>
  <li>Parallelize and Optimize
    <ul>
      <li>Run tests in parallel (e.g., Playwright, Cypress).</li>
      <li>Keep them minimal to avoid bloated CI runs.</li>
    </ul>
  </li>
  <li>Combine with Unit &amp; Integration Tests
    <ul>
      <li>Don’t rely solely on E2E.</li>
      <li>
        <p>Use a <strong>test pyramid</strong> strategy:</p>

        <ul>
          <li>70% Unit Tests</li>
          <li>20% Integration Tests</li>
          <li>10% E2E Tests</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Monitor and Review Regularly
    <ul>
      <li>E2E tests can get brittle.</li>
      <li>Review and refactor when the UI or workflows change.</li>
    </ul>
  </li>
</ol>

<h1 id="key-takeaways">Key Takeaways</h1>

<ul>
  <li><strong>E2E tests replicate the user’s journey</strong> and validate the full system.</li>
  <li>They are <strong>slower and more brittle</strong>, but they give the <strong>highest level of confidence</strong>.</li>
  <li>Use them sparingly for <strong>critical paths</strong> (login, payments, core workflows).</li>
  <li>
    <p>Balance your test pyramid:</p>

    <ul>
      <li>Many <strong>unit tests</strong></li>
      <li>Fewer <strong>integration tests</strong></li>
      <li>Very few but powerful <strong>E2E tests</strong></li>
    </ul>
  </li>
  <li>Choose the right tools for your stack (Selenium, Playwright, Cypress, Dusk).</li>
  <li>Follow best practices to keep tests reliable and maintainable.</li>
</ul>

]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/software-testing.webp" medium="image" />
  
  
    
      <category>Testing</category>
    
      <category>E2E-Testing</category>
    
      <category>Software Development</category>
    
  
  
    
      <category>Testing</category>
    
      <category>Software Development</category>
    
      <category>Quality Assurance</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Integration Testing in Depth : Test components working together (and not hate it) - Part 3</title>
      <link>https://billyokeyo.dev/posts/integration-testing/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/integration-testing/</guid>
      <pubDate>Fri, 05 Sep 2025 00:00:00 +0000</pubDate>
  
      <atom:updated>2025-09-15T02:46:26+00:00</atom:updated>
  
      <description><![CDATA[Integration tests sit in the sweet spot between tiny, fast unit tests and slow, expensive end-to-end tests. They verify that multiple parts of your system cooperate correctly, e.g., your API layer talks to the DB the way you expect, background jobs persist state, or your service correctly handles responses from an external API.]]></description>
      <content:encoded><![CDATA[<p>Integration tests sit in the sweet spot between tiny, fast unit tests and slow, expensive end-to-end tests. They verify that multiple parts of your system cooperate correctly e.g., your API layer talks to the DB the way you expect, background jobs persist state, or your service correctly handles responses from an external API.</p>

<p>This post is a practical, language-agnostic guide to integration testing, plus <strong>side-by-side, runnable patterns</strong> for <strong>Python</strong>, <strong>C# (.NET)</strong>, <strong>TypeScript (Node/Express)</strong> and <strong>PHP (Laravel)</strong> so you can immediately apply the ideas in your stack.</p>

<h2 id="what-integration-tests-are-and-are-not">What integration tests are (and are not)</h2>

<p><strong>Integration tests</strong> verify behavior across component boundaries, multiple classes, modules, services or infrastructure pieces that would not be exercised by a unit test.</p>

<p><strong>They are not:</strong></p>

<ul>
  <li>A replacement for unit tests (they’re slower &amp; coarser).</li>
  <li>Full UI-driven E2E tests (unless you intentionally include the UI).</li>
</ul>

<p><strong>They are good for:</strong></p>

<ul>
  <li>Verifying DB reads/writes via your data access layer.</li>
  <li>Testing service-to-service interactions.</li>
  <li>Ensuring message queue jobs and workers together produce expected state.</li>
  <li>Checking how your app handles external API payloads (with a mock or stub of that API).</li>
</ul>

<h2 id="goals--tradeoffs">Goals &amp; tradeoffs</h2>

<p><strong>Goals</strong></p>

<ul>
  <li>Catch bugs that only appear when components are wired together.</li>
  <li>Validate API contracts inside your own system.</li>
  <li>Give more realistic coverage than unit tests.</li>
</ul>

<p><strong>Tradeoffs</strong></p>

<ul>
  <li>Slower than unit tests.</li>
  <li>Harder to make fully deterministic (external services, timing).</li>
  <li>Need careful setup/teardown to stay reliable.</li>
</ul>

<h2 id="core-patterns--recommendations">Core patterns &amp; recommendations</h2>

<h3 id="1-use-realistic-but-controlled-dependencies">1. Use realistic but controlled dependencies</h3>

<ul>
  <li>Prefer a <strong>real database</strong> (or the same engine, e.g., PostgreSQL) rather than mocking DB calls.</li>
  <li>For external services (payment gateways, email providers), use <strong>service doubles</strong>: a local mock server, WireMock, or HTTP interceptors (nock, responses, Http::fake). Don’t call the live service in CI.</li>
</ul>

<h3 id="2-isolate-tests">2. Isolate tests</h3>

<ul>
  <li>Run each test in a transaction and roll it back (if possible), or recreate schema between tests.</li>
  <li>Or give each test its own ephemeral database (unique DB name/per-worker) when running tests in parallel.</li>
</ul>

<h3 id="3-keep-tests-focused">3. Keep tests focused</h3>

<ul>
  <li>Each integration test should exercise a meaningful interaction or flow (e.g., API -&gt; DB, or API -&gt; external-service-stub -&gt; DB), not every possible path.</li>
</ul>

<h3 id="4-seed-deterministic-test-data">4. Seed deterministic test data</h3>

<ul>
  <li>Use builders/fixtures to create known state. Avoid random data unless seeded.</li>
</ul>

<h3 id="5-manage-long-running-processes">5. Manage long-running processes</h3>

<ul>
  <li>For queues/workers, either run workers synchronously in tests, use a fake queue, or spin up a test worker process in CI.</li>
</ul>

<h3 id="6-use-testcontainers-or-docker-compose-in-ci">6. Use testcontainers or docker-compose in CI</h3>

<ul>
  <li>For close-to-production fidelity, use Testcontainers (or docker-compose) to provision real DBs and services in CI.</li>
</ul>

<h3 id="7-avoid-flaky-tests">7. Avoid flaky tests</h3>

<ul>
  <li>No sleeps/time-based races. Use blocking signals, polling with timeouts, or deterministic stubs.</li>
</ul>

<h2 id="when-to-mock-vs-when-to-use-real-services">When to mock vs when to use real services</h2>

<ul>
  <li><strong>Real DB</strong>: Prefer real DB engine (Postgres, MySQL). SQLite is OK for many cases but can mask engine-specific issues.</li>
  <li><strong>External APIs</strong>: Mock in integration tests. Use contract testing (Pact) to keep mocked expectations in-sync.</li>
  <li><strong>Caches/Queues</strong>: Use in-memory or test doubles unless you must validate the actual middleware behavior.</li>
</ul>

<h2 id="observability-make-debugging-failing-integration-tests-easy">Observability: make debugging failing integration tests easy</h2>

<ul>
  <li>Emit structured logs during tests (include request IDs).</li>
  <li>Capture and print responses and DB state on failure.</li>
  <li>Keep helpful assertion messages.</li>
</ul>

<h2 id="checklist-test-lifecycle">Checklist: test lifecycle</h2>

<ol>
  <li>Create test environment (DB, migrations applied).</li>
  <li>Seed minimal deterministic data.</li>
  <li>Execute action via real interfaces (HTTP client, direct call).</li>
  <li>Assert state persisted, side effects happened (e.g., DB row created, message pushed, HTTP call stubbed).</li>
  <li>Tear down (transaction rollback, truncate tables, drop DB).</li>
</ol>

<h2 id="common-pitfalls--fixes">Common pitfalls &amp; fixes</h2>

<ul>
  <li><strong>Flaky tests</strong>: avoid sleep-based waiting; use retry-with-timeout polling and assert deterministically.</li>
  <li><strong>Slow setup</strong>: keep per-test setup minimal; use transactional rollback where possible.</li>
  <li><strong>Parallel test collisions</strong>: give tests separate DBs or use unique table prefixes.</li>
  <li><strong>“Works locally but fails in CI”</strong>: mirror CI environment locally using Docker/Testcontainers and run tests there.</li>
</ul>

<h1 id="example-integration-tests-side-by-side">Example integration tests (side-by-side)</h1>

<p>Scenario used across examples:</p>

<blockquote>
  <p>A <code>POST /users</code> endpoint that creates a user record in the database and triggers an HTTP call to an external email service (welcome email). Integration test will create a user via HTTP, verify DB row exists, and verify the email call was made (mocked).</p>
</blockquote>

<h2 id="python--fastapi--sqlalchemy--pytest--requests-mock">Python — FastAPI + SQLAlchemy + pytest + requests-mock</h2>

<p><strong>Notes:</strong> Use <code>sqlite:///:memory:</code> or a Testcontainers Postgres for higher fidelity in CI. <code>requests-mock</code> stubs outgoing HTTP calls.</p>

<pre><code class="language-python"># app.py (pseudo)
from fastapi import FastAPI, Depends
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

app = FastAPI()

# App factory to pass different DB URLs in tests
def create_app(db_url, email_service_url):
    engine = create_engine(db_url)
    Session = sessionmaker(bind=engine)
    # create tables...
    app.state.db = Session
    app.state.email_url = email_service_url

    @app.post("/users")
    def create_user(payload: dict):
        sess = app.state.db()
        user = User(name=payload["name"], email=payload["email"])
        sess.add(user); sess.commit()
        # Send welcome email via requests.post(app.state.email_url, json=...)
        return {"id": user.id}

    return app
</code></pre>

<pre><code class="language-python"># test_integration.py
import pytest
from fastapi.testclient import TestClient
import requests_mock
from app import create_app

@pytest.fixture
def client(tmp_path):
    # Use sqlite in-memory for speed or file DB for persistence across app
    app = create_app("sqlite:///:memory:", "http://email.test/send")
    client = TestClient(app)
    yield client

def test_create_user_and_send_email(client):
    with requests_mock.Mocker() as m:
        m.post("http://email.test/send", status_code=200, json={"ok": True})
        resp = client.post("/users", json={"name":"Alice","email":"a@example.com"})
        assert resp.status_code == 200
        user_id = resp.json()["id"]

        # Verify DB row exists (open a session)
        Session = client.app.state.db
        sess = Session()
        user = sess.query(User).filter_by(id=user_id).one_or_none()
        assert user is not None
        assert user.email == "a@example.com"

        # Verify external email call occurred
        assert m.called
        assert m.request_history[0].json() == {"to": "a@example.com", "template": "welcome"}
</code></pre>

<p><strong>Tips</strong></p>

<ul>
  <li>For CI, replace sqlite with Testcontainers Postgres: <code>create_app(postgres_url, ...)</code>.</li>
  <li>Use DB migrations in setup if using a real DB.</li>
</ul>

<h2 id="c-net--aspnet-core--webapplicationfactory--inmemory-db--testcontainer">C# (.NET) — ASP.NET Core + WebApplicationFactory + InMemory DB / Testcontainer</h2>

<p><strong>Notes:</strong> Use <code>WebApplicationFactory&lt;TEntryPoint&gt;</code> to spin the app in tests and override service registrations for test doubles.</p>

<pre><code class="language-csharp">// In Startup.cs, app reads EmailService via IEmailService (HttpEmailService in prod)

public class TestEmailService : IEmailService {
    public List&lt;EmailMessage&gt; Sent = new();
    public Task SendAsync(EmailMessage msg) { Sent.Add(msg); return Task.CompletedTask; }
}

// Integration test
public class UsersIntegrationTests : IClassFixture&lt;WebApplicationFactory&lt;Program&gt;&gt; {
    private readonly WebApplicationFactory&lt;Program&gt; _factory;

    public UsersIntegrationTests(WebApplicationFactory&lt;Program&gt; factory) {
        _factory = factory.WithWebHostBuilder(builder =&gt; {
            builder.ConfigureServices(services =&gt; {
                // Replace real DB with in-memory or Testcontainer; replace email service with TestEmailService
                services.AddSingleton&lt;IEmailService, TestEmailService&gt;();
                // Configure EF Core to use InMemoryDatabase or connection string from Testcontainers
            });
        });
    }

    [Fact]
    public async Task PostUsers_CreatesUser_And_SendsEmail() {
        var client = _factory.CreateClient();
        var content = new StringContent("{\"name\":\"Bob\",\"email\":\"b@ex.com\"}", Encoding.UTF8, "application/json");
        var resp = await client.PostAsync("/users", content);
        resp.EnsureSuccessStatusCode();

        // Verify DB: use scope to resolve DbContext
        using(var scope = _factory.Services.CreateScope()) {
            var db = scope.ServiceProvider.GetRequiredService&lt;AppDbContext&gt;();
            var user = db.Users.Single(u =&gt; u.Email == "b@ex.com");
            Assert.NotNull(user);
        }

        // Verify TestEmailService captured message
        var emailService = _factory.Services.GetRequiredService&lt;IEmailService&gt;() as TestEmailService;
        Assert.Single(emailService.Sent);
        Assert.Equal("welcome", emailService.Sent[0].Template);
    }
}
</code></pre>

<p><strong>Tips</strong></p>

<ul>
  <li>For a real DB in CI, use <code>Testcontainers</code> .NET to spin up Postgres and set EF Core connection string.</li>
  <li>Overriding services avoids brittle HTTP stubbing, and keeps assertions in-process.</li>
</ul>

<h2 id="typescript-nodeexpress--supertest--sqlite-in-memory--nock">TypeScript (Node/Express) — supertest + sqlite in-memory + nock</h2>

<p><strong>Notes:</strong> <code>supertest</code> issues HTTP requests to your Express app instance. Use <code>nock</code> to intercept outgoing HTTP.</p>

<pre><code class="language-ts">// app.ts (pseudo)
import express from "express";
import bodyParser from "body-parser";
import { initDb } from "./db";

export function createApp(dbPath: string) {
  const app = express();
  app.use(bodyParser.json());
  const db = initDb(dbPath); // e.g., sqlite3 in-memory or file
  app.post("/users", async (req, res) =&gt; {
    const { name, email } = req.body;
    const id = await db.run("INSERT INTO users(name,email) VALUES(?,?)", [name, email]);
    // call external email service via fetch/http client
    await fetch("http://email.test/send", { method: "POST", body: JSON.stringify({ to: email, template: "welcome" }) });
    res.json({ id });
  });
  return app;
}
</code></pre>

<pre><code class="language-ts">// test/integration.test.ts
import request from "supertest";
import nock from "nock";
import { createApp } from "../app";
import { openDb, getUserById } from "../db";

describe("POST /users", () =&gt; {
  it("creates a user and calls email service", async () =&gt; {
    const app = createApp(":memory:");
    const email = nock("http://email.test")
      .post("/send", (body) =&gt; body.to === "c@ex.com" &amp;&amp; body.template === "welcome")
      .reply(200, { ok: true });

    const res = await request(app)
      .post("/users")
      .send({ name: "Carol", email: "c@ex.com" })
      .expect(200);

    // verify DB
    const user = await getUserById(res.body.id);
    expect(user.email).toBe("c@ex.com");

    // verify external call was made
    expect(email.isDone()).toBe(true);
  });
});
</code></pre>

<p><strong>Tips</strong></p>

<ul>
  <li>For complex schemas, use migrations in test setup or run a dedicated test DB with <code>sqlite</code> file per test.</li>
  <li>For Postgres in CI, spin up DB via docker-compose or Testcontainers Node.</li>
</ul>

<h2 id="php-laravel--http-tests--refreshdatabase--httpfake">PHP (Laravel) — HTTP tests + RefreshDatabase + Http::fake()</h2>

<p><strong>Notes:</strong> Laravel has excellent integration testing helpers. <code>RefreshDatabase</code> runs migrations and transacts where possible. Use <code>Http::fake()</code> to intercept external HTTP.</p>

<pre><code class="language-php">// routes/api.php
Route::post('/users', [UserController::class, 'store']);

// UserController-&gt;store uses User model and Http::post('http://email.test/send', ...);

</code></pre>

<pre><code class="language-php">// tests/Feature/CreateUserTest.php
&lt;?php
namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
use App\Models\User;

class CreateUserTest extends TestCase
{
    use RefreshDatabase;

    public function test_create_user_and_send_welcome_email()
    {
        Http::fake([
            'email.test/*' =&gt; Http::response(['ok' =&gt; true], 200),
        ]);

        $response = $this-&gt;postJson('/api/users', [
            'name' =&gt; 'Dan',
            'email' =&gt; 'd@ex.com',
        ]);

        $response-&gt;assertStatus(200);

        // verify DB
        $this-&gt;assertDatabaseHas('users', ['email' =&gt; 'd@ex.com']);

        // assert that an outbound call was made
        Http::assertSent(function ($request) {
            return $request-&gt;url() == 'http://email.test/send' &amp;&amp;
                   $request['to'] == 'd@ex.com' &amp;&amp;
                   $request['template'] == 'welcome';
        });
    }
}
</code></pre>

<p><strong>Tips</strong></p>

<ul>
  <li>Laravel’s <code>RefreshDatabase</code> will use in-memory sqlite if configured, otherwise migrate a test DB.</li>
  <li>Use <code>Queue::fake()</code> to test that jobs were dispatched without executing background workers.</li>
</ul>

<h1 id="practical-integration-testing-strategies">Practical integration testing strategies</h1>

<h3 id="use-transactions-for-isolation">Use transactions for isolation</h3>

<ul>
  <li>Wrap each test in a DB transaction and roll back at the end. Works well when everything runs in the same DB connection.</li>
  <li>Caveat: some ORMs/connections (e.g., tests that spawn separate processes) might not share transaction visibility.</li>
</ul>

<h3 id="use-in-memory-dbs-for-speed-but-test-on-real-dbs-in-ci">Use in-memory DBs for speed, but test on real DBs in CI</h3>

<ul>
  <li>SQLite in-memory is fast, but can behave differently (indexing, SQL dialect). Complement local tests with real-engine tests in CI using containers.</li>
</ul>

<h3 id="stubbing-external-http-reliably">Stubbing external HTTP reliably</h3>

<ul>
  <li>Python: <code>responses</code> or <code>requests-mock</code></li>
  <li>Node: <code>nock</code></li>
  <li>C#: WireMock.Net or replace typed <code>HttpClient</code> with test handler</li>
  <li>PHP: Laravel <code>Http::fake()</code></li>
</ul>

<h3 id="testcontainers--real-dependencies-in-ci">Testcontainers — real dependencies in CI</h3>

<ul>
  <li>Spin up a Postgres, Redis, or Kafka container for integration tests.</li>
  <li>Testcontainers exists for many ecosystems (Java, .NET, Node, Python wrappers).</li>
</ul>

<h3 id="contract-testing-for-cross-team-apis">Contract testing for cross-team APIs</h3>

<ul>
  <li>Use Pact or similar to generate contracts from consumer tests and verify provider compliance in provider CI. This avoids brittle mocks and catches breaking API changes.</li>
</ul>

<h3 id="background-jobs--queues">Background jobs &amp; queues</h3>

<ul>
  <li>Either run job handlers inline (synchronously) in tests, use fake queues that record enqueued messages, or run a worker process in CI that reads from test queue.</li>
</ul>

<h1 id="how-many-integration-tests-should-you-write">How many integration tests should you write?</h1>

<p>No fixed number. Aim for:</p>

<ul>
  <li>Unit tests: many (business logic)</li>
  <li>Integration tests: enough to cover <strong>critical integration points</strong> (DB persistence, payment flows, auth)</li>
  <li>E2E tests: few (critical user paths)</li>
</ul>

<p>A practical rule: write integration tests for <strong>each major DB operation and for the essential external integrations</strong>.</p>

<h1 id="debugging-failing-integration-tests">Debugging failing integration tests</h1>

<ul>
  <li>Print request/response bodies and DB rows on failure.</li>
  <li>Capture network traffic (or enable higher logging).</li>
  <li>Reproduce the failing test locally with the same CI container setup (Testcontainers makes that easy).</li>
  <li>If a test is flaky, add instrumentation and increase visibility; temporary retries mask real issues.</li>
</ul>

<h1 id="sample-integration-test-checklist">Sample integration test checklist</h1>

<p>Before merging an integration test into CI:</p>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Test uses deterministic data.</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />DB schema/migrations run and are applied in setup.</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />External dependencies are stubbed or provided by test containers.</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Test cleans up (transaction rollback or truncation).</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />No sleeps or time-based races.</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Test is focused on behavior, not implementation.</li>
</ul>

<h1 id="wrapping-up--next-steps">Wrapping up &amp; next steps</h1>

<p>Integration tests reduce the mismatch between isolated units and the full system. They give higher confidence than unit tests while being cheaper and faster than full E2E tests. When designed well they catch boundary problems early and make refactoring safer.</p>

<p><strong>Next post in the series:</strong> <em>End-to-End (E2E) Testing in Depth</em> — we’ll cover realistic end-to-end strategies, UI-driving vs API-only E2E, test environments, flaky UI tests, and how to design low-maintenance high-value E2E checks.</p>

<p><em>Happy testing!</em></p>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/software-testing.webp" medium="image" />
  
  
    
      <category>Testing</category>
    
      <category>Integration-Testing</category>
    
      <category>Software Development</category>
    
  
  
    
      <category>Testing</category>
    
      <category>Software Development</category>
    
      <category>Quality Assurance</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Unit Testing in Depth: Principles, Patterns, and Pragmatic Tactics - Part 2</title>
      <link>https://billyokeyo.dev/posts/unit-testing-in-depth/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/unit-testing-in-depth/</guid>
      <pubDate>Fri, 29 Aug 2025 00:00:00 +0000</pubDate>
  
      <atom:updated>2025-09-15T02:46:26+00:00</atom:updated>
  
      <description><![CDATA[Unit tests are the safety net that let you refactor without fear, document behavior without docs, and ship with confidence. Done well, they’re fast, reliable, and cheap to maintain. Done poorly, they’re flaky, slow, and ignored.]]></description>
      <content:encoded><![CDATA[<p>Unit tests are the safety net that let you refactor without fear, document behavior without docs, and ship with confidence. Done well, they’re fast, reliable, and cheap to maintain. Done poorly, they’re flaky, slow, and ignored.</p>

<p>This is a continuation of the the <strong>Unit, Integration, and End-to-End Tests: Building Confidence in Your Software</strong> series. In case you missed it, be sure to check out the previous posts for a solid foundation. This guide goes deep into <strong>what makes a great unit test</strong>, how to structure them, how to avoid common pitfalls, and how to design code that’s <em>easy</em> to unit test.</p>

<h2 id="what-is-a-unit-really">What is a “Unit” (really)?</h2>

<p>A <strong>unit</strong> is the smallest piece of behavior you care about verifying typically a function, method, or class. The unit:</p>

<ul>
  <li>Has <strong>no external I/O</strong> (no network, DB, filesystem, clock, randomness).</li>
  <li>Has <strong>clear inputs and outputs</strong> (return value or state change).</li>
  <li>Can be run thousands of times <strong>deterministically</strong> and <strong>quickly</strong>.</li>
</ul>

<blockquote>
  <p class="prompt-tip">Heuristic: if your test needs network access, sleeps, or a DB, it’s probably not a unit test.</p>
</blockquote>

<h2 id="the-goal-of-unit-tests">The Goal of Unit Tests</h2>

<ul>
  <li><strong>Prevent regressions</strong> close to the source of truth.</li>
  <li><strong>Document behavior</strong> by example.</li>
  <li><strong>Enable refactoring</strong> with confidence.</li>
  <li><strong>Encourage good design</strong> (loose coupling, small interfaces).</li>
</ul>

<h2 id="qualities-of-great-unit-tests-first">Qualities of Great Unit Tests (F.I.R.S.T.)</h2>

<ul>
  <li><strong>Fast</strong> – run in milliseconds; okay to run on every save/commit.</li>
  <li><strong>Isolated</strong> – no shared state; independent from other tests.</li>
  <li><strong>Repeatable</strong> – same result every run; no flakiness.</li>
  <li><strong>Self-validating</strong> – clear pass/fail without manual inspection.</li>
  <li><strong>Timely</strong> – written close to when the code is written (test-first or test-soon).</li>
</ul>

<h2 id="anatomy-of-a-readable-test">Anatomy of a Readable Test</h2>

<p>Use <strong>Arrange–Act–Assert (AAA)</strong> or <strong>Given–When–Then</strong>. Keep one behavior per test.</p>

<pre><code class="language-bash">test "calculate_total when valid items then returns sum minus discounts" {
  // Arrange
  items = [10, 20, 30]
  discount = 0.1
  sut = PriceCalculator() // SUT = System/Subject Under Test

  // Act
  total = sut.calculate_total(items, discount)

  // Assert
  assert_equal(54, total) // (10+20+30)=60; 10% off =&gt; 54
}
</code></pre>

<h3 id="naming-patterns-that-scale">Naming patterns that scale</h3>

<ul>
  <li><code>MethodName_WhenCondition_ShouldExpectedOutcome</code></li>
  <li><code>Given_State_When_Action_Then_Outcome</code></li>
</ul>

<p>Good names save hours of log-digging later.</p>

<h2 id="what-to-test-and-what-not-to">What to Test (and What Not to)</h2>

<p><strong>Do test</strong></p>

<ul>
  <li>Business rules and edge cases (boundaries, empties, nulls, negatives, overflows).</li>
  <li>Error handling (exceptions, validation messages).</li>
  <li>Idempotency and invariants (calling twice has same effect).</li>
  <li>Serialization/parsing for your own formats.</li>
</ul>

<p><strong>Don’t test</strong></p>

<ul>
  <li>Framework code you don’t control.</li>
  <li>Trivial getters/setters (unless there’s logic).</li>
  <li>UI layout or CSS (move to integration/E2E if needed).</li>
</ul>

<h2 id="test-data-without-pain">Test Data Without Pain</h2>

<p>Messy data setup is the #1 cause of unreadable tests. Use <strong>builders</strong>.</p>

<pre><code class="language-bash">order = OrderBuilder()
          .with_item("book", 20)
          .with_item("pen", 5)
          .with_discount(0.1)
          .build()
</code></pre>

<p>Patterns:</p>

<ul>
  <li><strong>Test Data Builder</strong>: fluent object construction.</li>
  <li><strong>Mother Object</strong>: canonical “valid” objects you tweak per test.</li>
  <li><strong>Parameterized tests</strong>: table-driven cases for variations.</li>
</ul>

<h2 id="test-doubles-choose-the-right-tool">Test Doubles: Choose the Right Tool</h2>

<p>Not everything should be “real” in a unit test. Replace collaborators with <strong>test doubles</strong>:</p>

<table>
  <thead>
    <tr>
      <th>Double</th>
      <th>Purpose</th>
      <th>Example</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Dummy</strong></td>
      <td>Filler to satisfy signatures</td>
      <td><code>new LoggerDummy()</code> that’s never used</td>
    </tr>
    <tr>
      <td><strong>Stub</strong></td>
      <td>Provide canned returns</td>
      <td><code>ClockStub(now="2025-08-22")</code></td>
    </tr>
    <tr>
      <td><strong>Spy</strong></td>
      <td>Record calls for later assertions</td>
      <td><code>EmailSpy.sent_to("a@b.com") == true</code></td>
    </tr>
    <tr>
      <td><strong>Mock</strong></td>
      <td>Pre-programmed expectations (behavior verification)</td>
      <td><code>Expect(repo.save(order))</code></td>
    </tr>
    <tr>
      <td><strong>Fake</strong></td>
      <td>Lightweight working impl</td>
      <td><code>InMemoryRepository</code> with a map</td>
    </tr>
  </tbody>
</table>

<p><strong>Guideline:</strong> Prefer <strong>stubs/spies/fakes</strong>. Use <strong>mocks</strong> when you truly care about the interaction contract (e.g., exactly one call, in a specific order). Over-mocking couples tests to implementation details.</p>

<h2 id="taming-non-determinism-time-randomness-concurrency-io">Taming Non-Determinism (Time, Randomness, Concurrency, I/O)</h2>

<p>Flaky unit tests usually leak non-determinism. Isolate it behind interfaces:</p>

<h3 id="time">Time</h3>

<p>Introduce a <code>Clock</code> port.</p>

<pre><code class="language-bash">interface Clock { now(): Instant }
class SystemClock implements Clock { now() = system_now() }
class FixedClock(t) implements Clock { now() = t }
</code></pre>

<p>Inject <code>FixedClock</code> in tests.</p>

<h3 id="randomness">Randomness</h3>

<p>Wrap the RNG:</p>

<pre><code class="language-bash">interface RandomGen { next(): int }
class SeededRandom(seed) implements RandomGen { ... }
class FixedRandom(sequence) implements RandomGen { next() = sequence.pop() }
</code></pre>

<h3 id="concurrency">Concurrency</h3>

<p>Avoid real threads in unit tests. Extract logic to pure functions; test scheduling via <strong>fake executors</strong> or <strong>synchronous dispatchers</strong>.</p>

<h3 id="io">I/O</h3>

<p>Abstract with ports/adapters (Repository, HttpClient, FileStore). Use <strong>in-memory fakes</strong>.</p>

<h2 id="assertions-that-pull-their-weight">Assertions that Pull Their Weight</h2>

<ul>
  <li>Prefer <strong>specific</strong> asserts: <code>assert_equal(54, total)</code> over <code>assert_true(total &lt; 60)</code>.</li>
  <li>Provide <strong>custom messages</strong>: <code>assert_equal(54, total, "10% discount not applied")</code>.</li>
  <li>Assert one <strong>behavior</strong> per test (multiple asserts okay if they describe one behavior).</li>
</ul>

<h2 id="property-based-tests-when-examples-arent-enough">Property-Based Tests (when examples aren’t enough)</h2>

<p>Beyond example tests, check <strong>properties</strong> that must always hold.</p>

<p>Properties for <code>calculate_total(items, d)</code>:</p>

<ul>
  <li><strong>Non-negativity:</strong> result ≥ 0</li>
  <li><strong>Monotonicity:</strong> adding an item never decreases total</li>
  <li><strong>Discount bounds:</strong> with <code>0 ≤ d ≤ 1</code>, total ≤ sum(items)</li>
</ul>

<pre><code class="language-bash">property "adding item increases total" {
  for_all(item_price &gt; 0, items &gt;= []) {
    before = calc(items, d=0)
    after  = calc(items + [item_price], d=0)
    assert_true(after &gt;= before)
  }
}
</code></pre>

<p>Use property tests to catch edge cases humans miss.</p>

<h2 id="structuring-your-test-suite">Structuring Your Test Suite</h2>

<ul>
  <li><strong>Mirror production structure</strong>: <code>src/price/calculator.*</code> → <code>test/price/calculator_test.*</code></li>
  <li><strong>One SUT per file</strong> where possible.</li>
  <li><strong>Common helpers</strong> in <code>test_support/</code> (builders, fakes, assertions).</li>
  <li>Keep <strong>fixtures local</strong> unless truly shared.</li>
</ul>

<h2 id="coverage-useful-but-not-the-goal">Coverage: Useful, but Not the Goal</h2>

<ul>
  <li>Track <strong>line/statement</strong> and <strong>branch</strong> coverage to find blind spots.</li>
  <li>Don’t chase 100%. Aim for <strong>meaningful coverage</strong> of business logic and risk areas.</li>
  <li>Complement with <strong>mutation testing</strong> if available (ensures tests can detect real changes).</li>
</ul>

<h2 id="test-driven-development-tdd-micro-cycles-that-improve-design">Test-Driven Development (TDD): Micro-cycles that Improve Design</h2>

<p><strong>Red → Green → Refactor</strong>:</p>

<ol>
  <li><strong>Red</strong>: write a failing test that expresses desired behavior.</li>
  <li><strong>Green</strong>: implement the simplest code to pass it.</li>
  <li><strong>Refactor</strong>: improve design with tests staying green.</li>
</ol>

<p>Even if you don’t TDD all the time, using short cycles on complex logic reduces waste and over-engineering.</p>

<h2 id="common-unit-test-smells-and-fixes">Common Unit Test Smells (and Fixes)</h2>

<ul>
  <li><strong>Brittle tests</strong> (fail after harmless refactors)
→ Assert on <strong>behavior</strong>, not internal calls/ordering (avoid over-mocking).</li>
  <li><strong>Mega setups</strong> (50-line arrange blocks)
→ Introduce <strong>builders</strong> and <strong>sensible defaults</strong>.</li>
  <li><strong>Hidden dependencies</strong> (time, singletons)
→ Add interfaces; inject <strong>Clock/Random/Config</strong>.</li>
  <li><strong>Flaky tests</strong> (sometimes fail)
→ Remove sleeps; use <strong>fixed clocks</strong>, <strong>fakes</strong>, and <strong>synchronous</strong> executors.</li>
  <li><strong>Logic in tests</strong> (ifs/loops)
→ Replace with <strong>parameterized tests</strong> or test data tables.</li>
</ul>

<h2 id="worked-example-end-to-end-unit-test-thought-process">Worked Example (End-to-End Unit Test Thought Process)</h2>

<p><strong>Requirement:</strong> “Calculate order total with per-item prices, optional percentage discount, and tax applied after discount.”</p>

<p>Rules:</p>

<ul>
  <li>Subtotal = sum(prices)</li>
  <li>Discounted = subtotal × (1 − discount) where 0 ≤ discount ≤ 1</li>
  <li>Total = round_to_cents(discounted × (1 + taxRate))</li>
</ul>

<h3 id="example-tests">Example tests</h3>

<p><strong>Happy path</strong></p>

<pre><code class="language-bash">test "applies 10% discount then 16% tax" {
  sut = PriceCalculator(taxRate=0.16)
  total = sut.total([100, 50], discount=0.10)
  // subtotal = 150; after discount = 135; with tax = 156.6
  assert_equal(156.60, total)
}
</code></pre>

<p><strong>Edge cases</strong></p>

<pre><code class="language-bash">test "empty items yields 0" {
  assert_equal(0.00, PriceCalculator(0.16).total([], discount=0))
}

test "discount is clamped between 0 and 1" {
  assert_equal(116.00, PriceCalculator(0.16).total([100], discount=2.0)) // treated as 1.0
  assert_equal(116.00, PriceCalculator(0.16).total([100], discount=-0.5)) // treated as 0.0
}
</code></pre>

<p><strong>Rounding behavior</strong></p>

<pre><code class="language-bash">test "rounds to nearest cent (bankers or half-up as spec'd)" {
  // define and lock rounding policy; assert explicit expected cents
}
</code></pre>

<p><strong>Property</strong></p>

<pre><code class="language-bash">property "adding an item never decreases total when discount fixed" { ... }
</code></pre>

<blockquote>
  <p>Note how tests pin down rounding, clamping, and the order of operations—classic sources of real-world bugs.</p>
</blockquote>

<h2 id="design-for-testability-so-unit-tests-are-easy">Design for Testability (so unit tests are easy)</h2>

<ul>
  <li><strong>Dependency Injection</strong>: pass collaborators (Clock, Repo) via constructor/params.</li>
  <li><strong>Pure Functions</strong>: push logic into pure units; keep side effects at the edges.</li>
  <li><strong>Small Interfaces</strong>: program to ports; adapters do I/O.</li>
  <li><strong>Single Responsibility</strong>: smaller units are easier to test and reason about.</li>
</ul>

<h2 id="performance-keep-tests-lightning-fast">Performance: Keep Tests Lightning-Fast</h2>

<ul>
  <li>Avoid costly setup; prefer <strong>in-memory</strong> collaborators.</li>
  <li>No sleeps/timeouts; fake the clock/scheduler.</li>
  <li>Run unit tests in a <strong>watch mode</strong> locally; keep CI under seconds for this suite.</li>
</ul>

<h2 id="when-to-delete-or-rewrite-tests">When to Delete or Rewrite Tests</h2>

<ul>
  <li>Requirements changed → Update tests to reflect new truth.</li>
  <li>Test asserts an implementation detail → Rewrite to assert behavior.</li>
  <li>Chronic flakiness → Fix root cause or remove; flaky tests destroy trust.</li>
</ul>

<h2 id="a-minimal-template-you-can-reuse">A Minimal Template You Can Reuse</h2>

<pre><code class="language-bash">suite PriceCalculatorTests:
  setup:
    tax = 0.16
    sut = PriceCalculator(taxRate=tax)

  test "returns zero for empty items":
    expect sut.total([], discount=0) == 0.00

  test "applies discount before tax":
    expect sut.total([100], discount=0.10) == 104.40

  test "clamps invalid discounts":
    expect sut.total([100], discount=2.0) == 116.00

  test "non-negativity property":
    for_all item_lists:
      expect sut.total(item_lists, discount=0) &gt;= 0
</code></pre>

<h2 id="checklist-before-you-commit">Checklist: Before You Commit</h2>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Test name reads like a spec (explains <em>when/then</em>).</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Only one behavior under test.</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />No hidden I/O/time/randomness.</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Clear, specific assertions (with messages).</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Fast (ms), repeatable, independent.</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Data setup is minimal and readable (builder/fixtures).</li>
</ul>

<h1 id="side-by-side-examples">Side-by-Side Examples</h1>

<p>We’ll use a simple scenario:</p>

<blockquote>
  <p>Function to calculate the <strong>discounted price</strong> given an original price and discount percentage.</p>
</blockquote>

<h3 id="python-pytest--unittest">Python (pytest / unittest)</h3>

<pre><code class="language-python"># discount.py
def apply_discount(price: float, discount: float) -&gt; float:
    if discount &lt; 0 or discount &gt; 100:
        raise ValueError("Discount must be between 0 and 100")
    return price - (price * discount / 100)

# test_discount.py
import pytest
from discount import apply_discount

def test_apply_discount_valid():
    assert apply_discount(100, 10) == 90

def test_apply_discount_zero_discount():
    assert apply_discount(100, 0) == 100

def test_apply_discount_invalid():
    with pytest.raises(ValueError):
        apply_discount(100, 150)
</code></pre>

<h3 id="c-xunit">C# (xUnit)</h3>

<pre><code class="language-csharp">// Discount.cs
public class Discount
{
    public static decimal ApplyDiscount(decimal price, decimal discount)
    {
        if (discount &lt; 0 || discount &gt; 100)
            throw new ArgumentException("Discount must be between 0 and 100");
      
        return price - (price * discount / 100);
    }
}

// DiscountTests.cs
using Xunit;

public class DiscountTests
{
    [Fact]
    public void ApplyDiscount_ValidDiscount_ReturnsDiscountedPrice()
    {
        var result = Discount.ApplyDiscount(100, 10);
        Assert.Equal(90, result);
    }

    [Fact]
    public void ApplyDiscount_ZeroDiscount_ReturnsSamePrice()
    {
        var result = Discount.ApplyDiscount(100, 0);
        Assert.Equal(100, result);
    }

    [Fact]
    public void ApplyDiscount_InvalidDiscount_ThrowsException()
    {
        Assert.Throws&lt;ArgumentException&gt;(() =&gt; Discount.ApplyDiscount(100, 150));
    }
}
</code></pre>

<h3 id="typescript-jest">TypeScript (Jest)</h3>

<pre><code class="language-typescript">// discount.ts
export function applyDiscount(price: number, discount: number): number {
  if (discount &lt; 0 || discount &gt; 100) {
    throw new Error("Discount must be between 0 and 100");
  }
  return price - (price * discount / 100);
}

// discount.test.ts
import { applyDiscount } from "./discount";

test("applyDiscount with valid discount", () =&gt; {
  expect(applyDiscount(100, 10)).toBe(90);
});

test("applyDiscount with zero discount", () =&gt; {
  expect(applyDiscount(100, 0)).toBe(100);
});

test("applyDiscount with invalid discount", () =&gt; {
  expect(() =&gt; applyDiscount(100, 150)).toThrow();
});
</code></pre>

<hr />

<h3 id="php-laravel--phpunit">PHP (Laravel / PHPUnit)</h3>

<pre><code class="language-php">// app/Services/DiscountService.php
&lt;?php
namespace App\Services;

class DiscountService {
    public function applyDiscount(float $price, float $discount): float {
        if ($discount &lt; 0 || $discount &gt; 100) {
            throw new \InvalidArgumentException("Discount must be between 0 and 100");
        }
        return $price - ($price * $discount / 100);
    }
}
</code></pre>

<pre><code class="language-php">// tests/Unit/DiscountServiceTest.php
&lt;?php
namespace Tests\Unit;

use App\Services\DiscountService;
use PHPUnit\Framework\TestCase;

class DiscountServiceTest extends TestCase
{
    public function testApplyDiscountValid() {
        $service = new DiscountService();
        $this-&gt;assertEquals(90, $service-&gt;applyDiscount(100, 10));
    }

    public function testApplyDiscountZero() {
        $service = new DiscountService();
        $this-&gt;assertEquals(100, $service-&gt;applyDiscount(100, 0));
    }

    public function testApplyDiscountInvalid() {
        $this-&gt;expectException(\InvalidArgumentException::class);
        $service = new DiscountService();
        $service-&gt;applyDiscount(100, 150);
    }
}
</code></pre>

<hr />

<h1 id="comparison-table">Comparison Table</h1>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>Unit Test Goal</th>
      <th>Python (pytest)</th>
      <th>C# (xUnit)</th>
      <th>TypeScript (Jest)</th>
      <th>PHP (PHPUnit)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Framework</strong></td>
      <td>Testing tool used</td>
      <td>pytest/unittest</td>
      <td>xUnit</td>
      <td>Jest</td>
      <td>PHPUnit</td>
    </tr>
    <tr>
      <td><strong>Isolation</strong></td>
      <td>Tests only the function logic</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td><strong>Error Handling</strong></td>
      <td>Verify invalid inputs raise exceptions/errors</td>
      <td><code>pytest.raises</code></td>
      <td><code>Assert.Throws</code></td>
      <td><code>toThrow()</code></td>
      <td><code>expectException</code></td>
    </tr>
    <tr>
      <td><strong>Speed</strong></td>
      <td>Very fast, no external dependency</td>
      <td>⚡⚡⚡</td>
      <td>⚡⚡⚡</td>
      <td>⚡⚡⚡</td>
      <td>⚡⚡⚡</td>
    </tr>
  </tbody>
</table>

<hr />

<h1 id="key-takeaways">Key Takeaways</h1>

<ul>
  <li><strong>Unit tests are your first safety net</strong>: they guarantee that individual building blocks work correctly.</li>
  <li><strong>They should be small, isolated, and run fast</strong>.</li>
  <li><strong>Mocks and stubs</strong> are often used when dependencies exist.</li>
  <li>Every language has its <strong>idiomatic testing framework</strong>, but the philosophy is the same.</li>
</ul>

<h2 id="whats-next-in-the-series">What’s Next in the Series</h2>

<p>Up next: <strong>Integration Testing in Depth</strong> — choosing boundaries, taming real dependencies, keeping tests reliable without turning them into E2E.
Stay tuned!</p>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/software-testing.webp" medium="image" />
  
  
    
      <category>Testing</category>
    
      <category>Unit-Testing</category>
    
      <category>Software Development</category>
    
  
  
    
      <category>Testing</category>
    
      <category>Software Development</category>
    
      <category>Quality Assurance</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>Unit, Integration, and End-to-End Tests: Building Confidence in Your Software - Part 1</title>
      <link>https://billyokeyo.dev/posts/unit-feature-integration-tests/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/unit-feature-integration-tests/</guid>
      <pubDate>Fri, 22 Aug 2025 00:00:00 +0000</pubDate>
  
      <atom:updated>2025-09-15T02:46:26+00:00</atom:updated>
  
      <description><![CDATA[Understanding the differences between unit, integration, and end-to-end tests is crucial for building robust software. This guide breaks down each type of test, their purposes, and best practices for implementation.]]></description>
      <content:encoded><![CDATA[<p>When building modern software systems, writing tests isn’t just about catching bugs, it’s about creating confidence. Confidence that your logic works, confidence that features integrate well, and confidence that the entire system behaves as expected for real users.</p>

<p>In the world of software testing, three types of tests are most commonly discussed: <strong>unit tests</strong>, <strong>integration tests</strong>, and <strong>end-to-end (E2E) tests</strong>. Each serves a distinct purpose, each has strengths and trade-offs, and together they form the foundation of a reliable testing strategy.</p>

<p>This article provides a <strong>comprehensive, language-agnostic breakdown</strong> of these three pillars of testing, highlighting their importance, differences, and best practices.</p>

<p><img src="{{site.baseurl}}/assets/img/gifs/bill-hicks-mic-tap.gif" alt="testing" /></p>

<h2 id="1-unit-tests-validating-the-smallest-pieces">1. Unit Tests: Validating the Smallest Pieces</h2>

<p><strong>Definition</strong>
Unit tests focus on testing the smallest units of code in isolation, typically functions, methods, or classes. The goal is to verify that a specific piece of logic behaves exactly as intended.</p>

<p><strong>Why they matter</strong></p>

<ul>
  <li>They provide <strong>fast feedback</strong> since they run quickly.</li>
  <li>They serve as <strong>documentation</strong> for how a function or module is supposed to work.</li>
  <li>They help <strong>catch regressions early</strong>, before code reaches higher environments.</li>
</ul>

<p><strong>Example Scenario</strong>
Imagine you’re building a shopping cart system. A unit test might check that:</p>

<ul>
  <li>Adding an item updates the cart total correctly.</li>
  <li>Removing an item decreases the count.</li>
  <li>Discount calculations apply properly.</li>
</ul>

<p>This test doesn’t care about the database, the API, or the user interface it just cares about whether your <code>calculateTotal(cartItems)</code> function works.</p>

<p><strong>Key Points for Good Unit Tests</strong></p>

<ul>
  <li>Keep them <strong>isolated</strong>—no databases, APIs, or file systems.</li>
  <li>Cover both <strong>happy paths</strong> and <strong>edge cases</strong>.</li>
  <li>Make them <strong>small and fast</strong> so they can run frequently.</li>
</ul>

<h2 id="2-integration-tests-verifying-modules-work-together">2. Integration Tests: Verifying Modules Work Together</h2>

<p><strong>Definition</strong>
Integration tests focus on testing how different modules, services, or components interact with each other. Unlike unit tests, they don’t isolate a single function they simulate realistic flows between parts of the system.</p>

<p><strong>Why they matter</strong></p>

<ul>
  <li>Many bugs don’t occur in isolation but at the <strong>boundaries</strong> where systems communicate.</li>
  <li>They help ensure that your code modules, services, or APIs <strong>play well together</strong>.</li>
  <li>They give a higher level of confidence than unit tests but at a higher cost (slower, more complex).</li>
</ul>

<p><strong>Example Scenario</strong>
In the shopping cart system, an integration test might check that:</p>

<ul>
  <li>When an item is added to the cart, it’s saved in the database.</li>
  <li>The updated cart total is correctly retrieved via the API.</li>
  <li>A discount applied in the service layer reflects in the final invoice.</li>
</ul>

<p>This test involves the database, service logic, and possibly the API layer.</p>

<p><strong>Key Points for Good Integration Tests</strong></p>

<ul>
  <li>Focus on <strong>real-world workflows</strong>, not individual functions.</li>
  <li>Use <strong>test doubles</strong> (e.g., mocks, stubs, in-memory databases) where necessary to keep them manageable.</li>
  <li>Balance depth—don’t turn every integration test into a full E2E test.</li>
</ul>

<h2 id="3-end-to-end-e2e-tests-testing-like-a-user">3. End-to-End (E2E) Tests: Testing Like a User</h2>

<p><strong>Definition</strong>
End-to-End tests validate the entire system from the user’s perspective. They simulate how a real user would interact with your application, covering everything from the front-end UI to the backend services and the database.</p>

<p><strong>Why they matter</strong></p>

<ul>
  <li>They catch issues that unit or integration tests miss.</li>
  <li>They ensure the <strong>entire flow works in production-like conditions</strong>.</li>
  <li>They give the <strong>highest level of confidence</strong> before release.</li>
</ul>

<p><strong>Example Scenario</strong>
For the shopping cart system, an E2E test might:</p>

<ol>
  <li>Open the application in a browser.</li>
  <li>Log in as a user.</li>
  <li>Add an item to the cart.</li>
  <li>Apply a discount code.</li>
  <li>Proceed to checkout and ensure the final invoice matches expectations.</li>
</ol>

<p>This test validates everything: frontend, backend, authentication, database, and even third-party services.</p>

<p><strong>Key Points for Good E2E Tests</strong></p>

<ul>
  <li>Keep them <strong>focused on critical user flows</strong> (checkout, login, payments).</li>
  <li>Minimize their number since they are <strong>slow and costly to maintain</strong>.</li>
  <li>Automate them in CI/CD but run them strategically (e.g., nightly builds, pre-release checks).</li>
</ul>

<h2 id="the-testing-pyramid-how-they-work-together">The Testing Pyramid: How They Work Together</h2>

<p>Think of these tests as layers in a pyramid:</p>

<ol>
  <li><strong>Unit tests</strong> form the base (most numerous, fastest).</li>
  <li><strong>Integration tests</strong> form the middle (fewer, slower).</li>
  <li><strong>E2E tests</strong> form the top (the least, but most comprehensive).</li>
</ol>

<p>This balance ensures:</p>

<ul>
  <li>You <strong>catch bugs early</strong> with unit tests.</li>
  <li>You <strong>validate real-world interactions</strong> with integration tests.</li>
  <li>You <strong>simulate user behavior</strong> with E2E tests.</li>
</ul>

<p><img src="{{site.baseurl}}/assets/img/testing-series/testing-pyramid.webp" alt="testing-pyramid" /></p>

<h2 id="best-practices-across-all-test-types">Best Practices Across All Test Types</h2>

<ul>
  <li><strong>Write clear, meaningful test names</strong> (e.g., <code>should_apply_discount_when_valid_code_provided</code>).</li>
  <li><strong>Aim for coverage, not 100% coverage obsession</strong>—focus on meaningful tests.</li>
  <li><strong>Automate tests in CI/CD</strong> pipelines for consistent feedback.</li>
  <li><strong>Keep tests deterministic</strong>—they should pass or fail for the same reason every time.</li>
  <li><strong>Continuously refactor tests</strong> just as you refactor code.</li>
</ul>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>Good testing is about balance. <strong>Unit tests</strong> give you speed, <strong>integration tests</strong> give you realism, and <strong>E2E tests</strong> give you confidence from a user’s perspective. Together, they help you ship reliable software faster and with less stress.</p>

<p>This article is the first in a <strong>testing series</strong> where we’ll dive deeper into each type, exploring practical strategies, pitfalls to avoid, and real-world examples.</p>

<blockquote>
  <p>In the next article, we’ll break down <strong>Unit Testing in depth</strong> covering patterns, anti-patterns, and practical tips for writing effective unit tests.</p>
</blockquote>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/software-testing.webp" medium="image" />
  
  
    
      <category>Testing</category>
    
      <category>Software Development</category>
    
  
  
    
      <category>Testing</category>
    
      <category>Software Development</category>
    
      <category>Quality Assurance</category>
    
  
    </item>

  
  





  



  


    <item>
      <title>How to Build Scalable Backend Systems with Python, C#, PHP and Dart</title>
      <link>https://billyokeyo.dev/posts/building-scalable-backend-systems/</link>
      <guid isPermaLink="true">https://billyokeyo.dev/posts/building-scalable-backend-systems/</guid>
      <pubDate>Mon, 18 Aug 2025 00:00:00 +0000</pubDate>
  
      <atom:updated>2025-08-18T00:00:00+00:00</atom:updated>
  
      <description><![CDATA[Scalability isn’t just about more servers—it’s about smarter design. From Python’s async APIs to C#’s enterprise performance, PHP’s proven reliability, and Dart’s async-first power—discover how to build backends that can scale to millions of users.]]></description>
      <content:encoded><![CDATA[<p>Building scalable backend systems is one of the biggest challenges for developers today. As applications grow, so does the demand for performance, reliability, and maintainability. Whether you’re building a SaaS product, an e-commerce platform, or a real-time chat application, scalability can make or break your system.</p>

<p>In this article, we’ll explore <strong>how to build scalable backend systems</strong> using  <strong>Python, C#, PHP, and Dart</strong> . We’ll look at architectural principles, performance tips, and practical examples in each language.</p>

<hr />

<h2 id="what-do-we-mean-by-scalable-backend">What Do We Mean by “Scalable Backend”?</h2>

<p>A <strong>scalable backend system</strong> is one that can handle <strong>increasing load (users, requests, or data)</strong> without major rewrites or performance bottlenecks. Scalability comes in two main flavors:</p>

<ul>
  <li><strong>Vertical Scaling (Scale Up):</strong> Adding more CPU, RAM, or resources to a single server.</li>
  <li><strong>Horizontal Scaling (Scale Out):</strong> Adding more servers or instances to distribute the load.</li>
</ul>

<p>The best backends are built with  <strong>scalability in mind from day one</strong> : modular, stateless, well-monitored, and optimized for both growth and resilience.</p>

<hr />

<h2 id="core-principles-of-scalable-backend-design">Core Principles of Scalable Backend Design</h2>

<p>No matter the language, scalable systems share some common patterns:</p>

<ol>
  <li><strong>Stateless Services</strong> → Each request should be independent. Store session data in Redis or a DB, not in memory.</li>
  <li><strong>Microservices Architecture</strong> → Break a monolithic app into smaller, independent services.</li>
  <li><strong>Load Balancing</strong> → Distribute requests evenly across multiple servers (NGINX, HAProxy, AWS ELB).</li>
  <li><strong>Caching</strong> → Use Redis, Memcached, or CDNs to reduce database load.</li>
  <li><strong>Database Scalability</strong> → Sharding, replication, and read-write separation.</li>
  <li><strong>Asynchronous Processing</strong> → Offload long tasks to queues (RabbitMQ, Kafka, Celery, Hangfire).</li>
  <li><strong>Monitoring &amp; Logging</strong> → Use Prometheus, ELK stack, or Grafana for insights.</li>
</ol>

<hr />

<h2 id="python-flexibility-and-rapid-development">Python: Flexibility and Rapid Development</h2>

<p>Python is loved for its <strong>developer-friendly syntax</strong> and  <strong>rich ecosystem</strong> . While not the fastest language, Python excels when paired with the right tools.</p>

<h3 id="python-stack-for-scalable-backends">Python Stack for Scalable Backends</h3>

<ul>
  <li><strong>Frameworks:</strong> Django (monolithic but battle-tested), FastAPI (modern and async-friendly), Flask (lightweight).</li>
  <li><strong>Async Support:</strong> <code>asyncio</code>, <code>uvicorn</code>, <code>gunicorn</code> for concurrent request handling.</li>
  <li><strong>Task Queues:</strong> Celery + Redis for background jobs.</li>
</ul>

<h3 id="example-async-endpoint-with-fastapi">Example: Async Endpoint with FastAPI</h3>

<pre><code class="language-python">from fastapi import FastAPI
import httpx

app = FastAPI()

@app.get("/users")
async def get_users():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://jsonplaceholder.typicode.com/users")
    return response.json()
</code></pre>

<p>This async API can handle <strong>thousands of concurrent requests</strong> without blocking.</p>

<hr />

<h2 id="c-enterprise-grade-performance">C#: Enterprise-Grade Performance</h2>

<p>C# with <strong>.NET Core</strong> is a powerhouse for scalable, enterprise-grade systems. It’s  <strong>fast, strongly typed, and built for concurrency</strong> .</p>

<h3 id="c-stack-for-scalability">C# Stack for Scalability</h3>

<ul>
  <li><strong>Framework:</strong> ASP.NET Core (cross-platform, high-performance).</li>
  <li><strong>Async Support:</strong> Built-in async/await.</li>
  <li><strong>Background Jobs:</strong> Hangfire, Azure Functions, or MassTransit.</li>
  <li><strong>Deployment:</strong> Docker + Kubernetes, Azure App Service.</li>
</ul>

<h3 id="example-async-api-in-aspnet-core">Example: Async API in ASP.NET Core</h3>

<pre><code class="language-csharp">[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly HttpClient _client;

    public UsersController(HttpClient client)
    {
        _client = client;
    }

    [HttpGet]
    public async Task&lt;IActionResult&gt; GetUsers()
    {
        var response = await _client.GetStringAsync("https://jsonplaceholder.typicode.com/users");
        return Ok(response);
    }
}
</code></pre>

<p>With built-in <strong>dependency injection</strong> and async/await, ASP.NET Core scales smoothly in production.</p>

<hr />

<h2 id="php-still-relevant-and-scalable">PHP: Still Relevant and Scalable</h2>

<p>Many dismiss PHP as “legacy,” but with  <strong>modern frameworks and PHP 8</strong> , it’s extremely fast and widely deployed.</p>

<h3 id="php-stack-for-scalability">PHP Stack for Scalability</h3>

<ul>
  <li><strong>Frameworks:</strong> Laravel, Symfony.</li>
  <li><strong>Async Support:</strong> Swoole or ReactPHP for non-blocking I/O.</li>
  <li><strong>Queues:</strong> Laravel Horizon, RabbitMQ, Redis.</li>
  <li><strong>Deployment:</strong> NGINX + PHP-FPM, Docker, or AWS Lambda (Bref).</li>
</ul>

<h3 id="example-scalable-api-with-laravel">Example: Scalable API with Laravel</h3>

<pre><code class="language-php">Route::get('/users', function () {
    return Http::get("https://jsonplaceholder.typicode.com/users")-&gt;json();
});
</code></pre>

<p>Add <strong>Redis caching</strong> and <strong>Laravel Horizon</strong> to process jobs, and PHP apps scale to  <strong>millions of requests per day</strong> .</p>

<hr />

<h2 id="dart-the-new-player-for-backend-development">Dart: The New Player for Backend Development</h2>

<p>Dart is best known for  <strong>Flutter mobile apps</strong> , but with <strong>Dart Frog</strong> and  <strong>Shelf</strong> , it’s emerging as a backend option. Its <strong>async-first nature</strong> makes it perfect for scalable APIs.</p>

<h3 id="dart-stack-for-scalability">Dart Stack for Scalability</h3>

<ul>
  <li><strong>Frameworks:</strong> Shelf, Dart Frog.</li>
  <li><strong>Async I/O:</strong> Built-in <code>async/await</code>.</li>
  <li><strong>Deployment:</strong> Docker + Kubernetes, Firebase Functions.</li>
</ul>

<h3 id="example-simple-dart-shelf-api">Example: Simple Dart Shelf API</h3>

<pre><code class="language-dart">import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;

void main() async {
  var handler = const Pipeline()
      .addMiddleware(logRequests())
      .addHandler((Request req) {
        return Response.ok('Hello, scalable world!');
      });

  var server = await io.serve(handler, InternetAddress.anyIPv4, 8080);
  print('Server running on http://${server.address.host}:${server.port}');
}
</code></pre>

<p>Dart’s async model is similar to Node.js, but with <strong>strong typing</strong> and better  <strong>performance consistency</strong> .</p>

<hr />

<h2 id="comparing-the-four-languages">Comparing the Four Languages</h2>

<table>
  <thead>
    <tr>
      <th>Language</th>
      <th>Strengths</th>
      <th>Best Use Cases</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Python</strong></td>
      <td>Fast prototyping, AI/ML integration, async APIs (FastAPI)</td>
      <td>Data-heavy apps, startups</td>
    </tr>
    <tr>
      <td><strong>C#</strong></td>
      <td>Enterprise-grade, high performance, async-first</td>
      <td>Large-scale enterprise apps, fintech</td>
    </tr>
    <tr>
      <td><strong>PHP</strong></td>
      <td>Huge ecosystem, Laravel magic, easy deployment</td>
      <td>E-commerce, CMS, SaaS</td>
    </tr>
    <tr>
      <td><strong>Dart</strong></td>
      <td>Async-first, integrates with Flutter, growing ecosystem</td>
      <td>Mobile-first apps, experimental backends</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="best-practices-for-scaling-backends-any-language">Best Practices for Scaling Backends (Any Language)</h2>

<ol>
  <li>Use <strong>APM tools</strong> (Datadog, New Relic) for performance monitoring.</li>
  <li>Implement <strong>circuit breakers</strong> (Hystrix pattern) to prevent cascading failures.</li>
  <li>Automate <strong>CI/CD pipelines</strong> for smooth deployments.</li>
  <li>Prefer <strong>event-driven architecture</strong> for real-time scalability.</li>
  <li>Benchmark with <strong>load testing tools</strong> (k6, JMeter, Locust).</li>
</ol>

<hr />

<h2 id="final-thoughts">Final Thoughts</h2>

<p>Building a scalable backend isn’t about picking the “best” language—it’s about designing with <strong>scalability principles</strong> in mind.</p>

<ul>
  <li>Python gives you flexibility and speed of development.</li>
  <li>C# delivers enterprise stability and blazing performance.</li>
  <li>PHP remains a battle-tested workhorse with a huge ecosystem.</li>
  <li>Dart offers exciting opportunities for mobile-first backends.</li>
</ul>

<blockquote>
  <p>The key takeaway: <strong>Focus on architecture first, language second.</strong></p>
</blockquote>
]]></content:encoded>
      <author>billycartel360@gmail.com (Billy Okeyo)</author>
  
      <media:content url="https://billyokeyo.dev/assets/img/backend-systems.jpg" medium="image" />
  
  
    
      <category>Web Development</category>
    
      <category>Programming</category>
    
  
  
    
      <category>Web Development</category>
    
      <category>Programming</category>
    
      <category>Backend Development</category>
    
      <category>Scalability</category>
    
  
    </item>

  </channel>
</rss>