<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Benjamin Cane — #Bengineering</title>
    <link>https://bencane.com</link>
    <description>Practical engineering notes from Benjamin Cane, usually shared on LinkedIn first and kept readable on the open web.</description>
    <language>en</language>
    
      <image>
        <url>https://bencane.com/assets/images/bengineering-card.png</url>
        <title>Benjamin Cane — #Bengineering</title>
        <link>https://bencane.com</link>
      </image>
    
    
      <lastBuildDate>Thu, 13 Aug 2026 24:00:00 GMT</lastBuildDate>
    
    
      
      
      
      
        
      
      <item>
        <title>“We can’t run locally” is usually a design smell</title>
        <link>https://bencane.com/posts/2026-08-13-run-locally-design-smell/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-08-13/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>“We can’t run locally” is usually a design smell.</p>
<p>I’m a believer that, as an engineer, you should be able to run your software locally.
But I hear it often: “We can’t run locally because of some reason.”</p>
<p>Sometimes it’s valid.
There are architectures and platforms out there that prevent running locally.
But more often than not, when it comes to backend distributed systems, it’s a design or implementation decision that nobody has challenged.</p>
<h2>Why This Matters</h2>
<p>Everyone these days is focused on speeding up software delivery.
Today, that generally means coding agents, <code>AI</code> tooling, and code generation.</p>
<p>But writing code faster doesn’t matter much if validating a one-line change takes an hour.</p>
<p>The speed of software delivery is often dictated by validation, not code generation.</p>
<h2>The Problem with Shared Dev Environments</h2>
<p>Many teams still rely on shared development environments as their primary way of validating changes.</p>
<p>That process usually looks something like this:</p>
<ul>
<li>Make a change</li>
<li>Commit the change</li>
<li>Push the branch</li>
<li>Wait for a build</li>
<li>Wait for a deployment</li>
<li>Run tests</li>
</ul>
<p>If everything goes well, you can validate your change.
If a mistake was made, or something doesn’t work right, you have to fix it and start over.</p>
<p>The result is a slow feedback loop, which can sometimes influence behavior.</p>
<p>The more friction there is to validate a change, the larger that change becomes.
If testing takes a long time, you will naturally focus on making sure everything is perfect before spending time validating.</p>
<h2>Local Validation Influences Good Behavior</h2>
<p>Compare the above process to one that uses a locally running service.</p>
<ul>
<li>Make a change</li>
<li>Build</li>
<li>Start the service</li>
<li>Run tests</li>
</ul>
<p>The process is dramatically shorter and, more importantly, faster.
This encourages engineers to make smaller changes, test more frequently, iterate their implementations, and find mistakes earlier.</p>
<p>This all results in better software.</p>
<h2>The Excuses</h2>
<p>“My service depends on too many other services.”</p>
<p>Run the services you own locally.
Mock the services you don’t.</p>
<p>“I need a database or message broker.”</p>
<p>Run those locally too.
There’s very likely a Docker container for each of them.</p>
<p>“We use cloud services.”</p>
<p>This one can be harder, but emulators exist for some services.
Other services might have a Dockerized open-source alternative.
For those that don’t, you could mock them.</p>
<p>There will always be exceptions: mainframes, specialized hardware, or unique managed services.</p>
<p>But I find many teams jump straight to “we can’t run locally” before they’ve seriously explored how they could.</p>
<h2>Running Local Doesn’t Mean Everything</h2>
<p>You don’t need to recreate your entire production environment on a laptop, though if you can, go for it.</p>
<p>The goal is to validate your change without depending on a shared or non-local environment that takes forever to test against.</p>
<p>Local development isn’t about recreating production.
It’s about creating enough of the environment to validate your change.
If that means turning off some functionality or creating mock services, it might be worth it.</p>
<h2>Why This Matters Even More Now</h2>
<p>Validating changes quickly is becoming more important as teams adopt coding agents.
An agent can generate code in seconds.</p>
<p>But if every iteration with your coding agent requires committing, pushing, building, deploying, and waiting, the feedback loop becomes the bottleneck.</p>
<h2>Final Thoughts</h2>
<p>Agents make mistakes.
Humans make mistakes.</p>
<p>The faster you can validate a change, the faster you can correct those mistakes.
That’s why local execution isn’t just a convenience.</p>
<p>It’s one of the most effective ways to shorten the feedback loop.</p>
]]></description>
        <pubDate>Thu, 13 Aug 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-08-13-run-locally.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>To make a service more stable, eliminate dependencies</title>
        <link>https://bencane.com/posts/2026-08-06-eliminate-dependencies/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-08-06/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>To make a service more stable, eliminate dependencies.</p>
<p>One of the simplest reliability rules I’ve learned is this:
Every dependency is another way for your service to fail.</p>
<h2>Why Dependencies Matter</h2>
<p>Every service has dependencies.</p>
<ul>
<li>Databases</li>
<li>Caches</li>
<li>Configuration services</li>
<li>Secrets managers</li>
<li>Logging pipelines</li>
<li>Tracing backends</li>
</ul>
<p>All of these dependencies can fail, and when they do, the typical service will fail with them.</p>
<p>The more dependencies a service has, the more failures it inherits.
Every dependency adds features, but it also adds failure modes.</p>
<p>Reliability is often about deciding which dependencies are actually worth the tradeoff.</p>
<h2>Why Edge Systems Tend to Be Dependency-Light</h2>
<p>I recently wrote about how systems closest to the customer carry the greatest responsibility for availability.</p>
<p>This is one reason edge systems tend to be dependency-light.</p>
<p>Load balancers, API gateways, and routers are responsible for availability.</p>
<p>Every dependency added to these systems creates another opportunity to take down the entire platform.
So they tend to avoid dependencies whenever possible.</p>
<h2>Eliminating Dependencies Isn’t Always Necessary</h2>
<p>Sometimes removing a dependency entirely isn’t practical.</p>
<p>A better question is:</p>
<ul>
<li>What happens if the dependency disappears?</li>
<li>Can the service continue operating?</li>
<li>Can it use cached data?</li>
<li>Can it fall back to a last-known-good configuration?</li>
<li>Can it degrade gracefully?</li>
</ul>
<p>If the answer is yes, you’ve significantly improved reliability even though the dependency still exists.</p>
<h2>A Real-World Example</h2>
<p>Take Envoy Proxy.
Envoy can receive configuration from an <code>xDS</code> service.
But it doesn’t call <code>xDS</code> for every request.</p>
<p>Instead:</p>
<ul>
<li>Configuration is fetched periodically</li>
<li>Stored in memory</li>
<li>Used locally during request processing</li>
</ul>
<p>If the <code>xDS</code> service becomes unavailable, Envoy continues routing traffic using its last known configuration.
The dependency still exists, but request processing no longer depends on its availability.</p>
<p>That’s a very different reliability model.</p>
<h2>Don’t Make Observability a Hard Dependency</h2>
<p>One of the most common mistakes I see is making observability a required dependency.</p>
<p>If your logging or tracing backend becomes unavailable, should customer traffic stop flowing?
No.</p>
<p>In most cases, observability should be a best effort.</p>
<p>Use asynchronous logging, buffering, and truncation policies so customer traffic continues to flow even when your observability platform is experiencing issues.</p>
<p>Operational visibility is important, but customer availability is more important.</p>
<h2>Final Thoughts</h2>
<p>You’ll never eliminate every dependency.
But you can eliminate unnecessary ones.</p>
<p>And for the remaining dependencies, you can design your service to survive failures.</p>
<p>The most reliable services aren’t dependency-free.
They’re designed to survive dependency failures.</p>
]]></description>
        <pubDate>Thu, 06 Aug 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-08-06-eliminate-dependencies.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Caching isn’t hard. Some data is hard to cache</title>
        <link>https://bencane.com/posts/2026-07-30-caching-data-tradeoffs/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-07-30/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Caching isn’t hard.
Some data is hard to cache.</p>
<p>You’ve all heard the advice: “Avoid caching because caching is difficult to get right.”</p>
<p>I agree with part of that statement.
Caching can absolutely be difficult.
But I think the reality is more nuanced.</p>
<p>The difficulty level of caching depends heavily on the type of data you are caching.</p>
<h2>Not All Data Is Equal</h2>
<p>When engineers talk about caching complexity, they’re usually thinking about data that changes frequently and requires strong consistency.</p>
<p>That’s the hardest kind of data to cache.
Because you need to answer some difficult questions.</p>
<ul>
<li>How do you invalidate the cache?</li>
<li>How do you handle updates?</li>
<li>What happens when the cache population fails?</li>
<li>How stale is too stale?</li>
</ul>
<p>These problems are real, but they don’t apply to all data.</p>
<h2>Frequently Updated Data</h2>
<p>Some data changes constantly and has strict accuracy requirements.
Account balances, inventory counts, active orders, and similar records.
This is where caching is difficult.</p>
<p>Every stale read has a consequence, and in some cases, it’s not worth the complexity.</p>
<h2>Infrequently Updated Data</h2>
<p>Some data changes occasionally, once an hour, once a day, or even longer.
Configuration data, product catalogs, country codes, and similar records.</p>
<p>In these cases, a small amount of staleness might be fine.
If data changes hourly and your cache refreshes every few minutes, that’s often a reasonable tradeoff.
The less frequently data changes, the less sensitive you become to cache staleness.</p>
<p>Caching is much easier with infrequently updated data.</p>
<h2>Immutable Data</h2>
<p>Immutable data is the easiest to cache.
Because it never changes.
Ledger entries, event records, receipts, and other immutable records.</p>
<p>Once immutable data is loaded into a cache, cache invalidation largely disappears as a problem.
Cache complexity for immutable data is more about balancing performance and hit-miss ratios.</p>
<p>Most of the time, immutable data is an ideal candidate for caching.</p>
<h2>The Real Question</h2>
<p>Instead of asking, “Should I cache?”
Ask yourself, “How often does this data change?
How accurate does it need to be?”</p>
<p>The answers will give you an idea of how complex caching will be.</p>
<h2>Final Thoughts</h2>
<p>Caching isn’t inherently hard.</p>
<p>Maintaining correctness for frequently changing data is hard.</p>
<p>The more stable the data, the less complex the caching needs to be.</p>
<p>Like most things in system design, you can’t just take all-or-none answers like “always cache” or “never cache.”
It’s important to understand the trade-offs, constraints, and consistency requirements of the data you’re working with.</p>
]]></description>
        <pubDate>Thu, 30 Jul 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-07-30-caching-data-tradeoffs.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>The closer to the edge, the more stable a platform must be</title>
        <link>https://bencane.com/posts/2026-07-23-edge-platform-stability/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-07-23/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>The closer to the edge, the more stable a platform must be.</p>
<p>The closer a component is to the customer, the greater its responsibility for keeping the entire platform available, even when everything behind it is having a bad day.</p>
<h2>Not All Services Carry the Same Reliability Burden</h2>
<p>Let’s consider a typical platform.</p>
<p>Customer -&gt; Load Balancer -&gt; API Gateway -&gt; Orchestrator -&gt; Microservices -&gt; Database</p>
<p>Every layer has a different job.
But every layer also has a different level of responsibility for resiliency.</p>
<p>As you move toward the customer, that responsibility increases.</p>
<h2>Deep Services Focus on Business Logic</h2>
<p>At the deepest layers of the platform, services are usually focused on business capabilities.</p>
<p>They process orders, transfer money, manage inventory, and store data.</p>
<p>These services often have databases, business rules, stateful operations, and multiple dependencies.</p>
<p>Resiliency matters, but it’s often focused on correctness.</p>
<p>If a database call fails:</p>
<ul>
<li>Should the transaction roll back?</li>
<li>Should the service fail over?</li>
<li>Should a compensating transaction occur?</li>
</ul>
<p>These services are primarily concerned with business outcomes.</p>
<h2>The Middle Layers Absorb Failures</h2>
<p>Move up a layer, and you often find orchestrators and workflow services.
These components coordinate work across multiple services.</p>
<p>If one service fails, the orchestrator may retry, execute fallback logic, trigger compensating actions, or roll back a workflow.
Their job is not just executing business logic, it’s ensuring execution succeeds despite failures.</p>
<h2>The Edge Exists to Protect Everything Behind It</h2>
<p>At the edge, things change.</p>
<p>Load balancers and API gateways are often stateless, dependency-light, highly available, and extremely fast.</p>
<p>Why?</p>
<p>Because their primary responsibility is availability.
Everything behind them is allowed to fail, and they absorb as much of that failure as possible.</p>
<p>They:</p>
<ul>
<li>Route around failures</li>
<li>Shed load</li>
<li>Fail over traffic</li>
<li>Enforce timeouts</li>
<li>Apply retries</li>
<li>Protect backend systems</li>
</ul>
<p>The edge isn’t just resilient for itself.
It’s resilient on behalf of everything behind it.</p>
<h2>Final Thoughts</h2>
<p>The deepest services in a platform should be focused on business logic.
The edge should be focused on availability.</p>
<p>The more failures your edge can absorb, the less every downstream service needs to care.
That’s why the closer you get to the customer, the more stable the platform must become.</p>
]]></description>
        <pubDate>Thu, 23 Jul 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-07-23-edge-platform-stability.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Sometimes the most resilient thing a system can do isn’t retry</title>
        <link>https://bencane.com/posts/2026-07-16-compensating-transactions/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-07-16/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Sometimes the most resilient thing a system can do isn’t retry.</p>
<p>Most resiliency discussions focus on retries, timeouts, and circuit breakers.</p>
<p>But some of the most important resiliency patterns happen after the failure.</p>
<p>That’s where compensating transactions come in.</p>
<h2>Resiliency Is About Recovery</h2>
<p>A common mistake is thinking resiliency means preventing failures.</p>
<p>Failures are inevitable.</p>
<p>Networks fail.
Services crash.
Messages get lost.
Requests time out.</p>
<p>True resiliency is accepting that failures will occur and having a plan to recover when they do.</p>
<p>Sometimes recovery means correcting actions that may already have occurred.</p>
<h2>A Real-World Example</h2>
<p>Have you ever swiped your card at a store, received a charge notification, only to have the terminal report an error and later discover that the charge was nowhere to be found?</p>
<p>What happened?</p>
<p>The point-of-sale terminal sent an authorization request.</p>
<p>But before it received a response, something failed: a network issue, a timeout, or a problem somewhere in the payment flow.</p>
<p>Now the terminal doesn’t know whether the transaction succeeded or failed.</p>
<p>The authorization request may have reached the card issuer and been processed, or it may not have.</p>
<p>Rather than risk leaving the customer incorrectly charged, the terminal sends a second transaction: a reversal.</p>
<h2>The Compensating Transaction</h2>
<p>That reversal is a real-world example of a compensating transaction.</p>
<p>Its purpose is simple:</p>
<p>Undo the effects of a previous action if that action completed successfully.
If the original authorization never happened, nothing changes.</p>
<p>If it did happen, the reversal corrects it.
Instead of determining exactly what happened, the system performs a corrective action.</p>
<h2>Beyond Payments</h2>
<p>Compensating transactions show up in many real-world distributed systems:</p>
<ul>
<li>Releasing inventory after a failed order</li>
<li>Refunding a payment after a fulfillment failure</li>
<li>Canceling a reservation when verification times out</li>
</ul>
<p>The pattern is always the same: something failed, and the system takes a corrective action.</p>
<h2>Final Thoughts</h2>
<p>When engineers think about resiliency, they often focus on preventing failures.
But distributed systems fail in unexpected ways.</p>
<p>Sometimes the most resilient thing a system can do isn’t retry.
It’s correct the mistake and move forward.</p>
]]></description>
        <pubDate>Thu, 16 Jul 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-07-16-compensating-transactions.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Should retries and timeouts live in your application or your service mesh?</title>
        <link>https://bencane.com/posts/2026-07-09-platform-vs-application-resiliency/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-07-09/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Should retries and timeouts live in your application or your service mesh?</p>
<p>This debate comes up constantly.</p>
<p>Should resiliency live in the platform components, or should the application own it?</p>
<p>Like most things in distributed systems, the answer is: It depends.</p>
<h2>Infrastructure Understands Traffic</h2>
<p>Service meshes, API gateways, and load balancers are great at handling generic resiliency concerns.</p>
<p>Things like:</p>
<ul>
<li>Connection timeouts</li>
<li>Automatic retries</li>
<li>Circuit breakers</li>
<li>Request-level failover</li>
</ul>
<p>The advantage is obvious.
You remove complexity from the application and apply resiliency consistently across services.</p>
<p>For many scenarios, this is the right answer.</p>
<h2>Infrastructure Doesn’t Understand Intent</h2>
<p>The challenge is that platform services only understand traffic.
They don’t understand why the request is made.</p>
<p>They don’t know whether a request is reading customer profile data, reserving inventory, transferring money, or uploading cat videos.</p>
<p>To the service mesh, they’re all just requests.
Some requests might even have custom timeouts and retries configured.
Most of the time, that’s fine.</p>
<p>Sometimes, it’s not.</p>
<h2>Context-Aware Resiliency</h2>
<p>Some resiliency decisions require application context.</p>
<p>Consider a financial transaction.
Should a timeout trigger a retry?
Maybe.</p>
<p>Does the request have an idempotency key?
If yes, blindly retrying might be safe.
If not, things become more complicated.</p>
<p>Did the request reach the downstream system?
Was it partially processed?</p>
<p>Do you need a compensating transaction before retrying?</p>
<p>At this point, the retry is no longer a networking decision.
It’s a business decision.
And business decisions belong in the application.</p>
<h2>The Real Answer</h2>
<p>The best architectures usually use both approaches.</p>
<p>Use platform resiliency whenever the decision can be made without application context.
But when correctness depends on understanding the request itself, move that logic into the application.</p>
<p>There should be a strong preference toward offloading complexity when possible.
Just don’t offload decisions that require business context.</p>
<h2>Final Thoughts</h2>
<p>I often see teams over-index on platform-level resiliency.
And while reducing application complexity is valuable, it isn’t free.</p>
<p>Infrastructure understands traffic and request characteristics.
Applications understand business intent.</p>
<p>When resiliency decisions depend on business intent, they belong in the application.</p>
]]></description>
        <pubDate>Thu, 09 Jul 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-07-09-platform-vs-application-resiliency.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Need to migrate from one database to another without downtime?</title>
        <link>https://bencane.com/posts/2026-07-02-dual-writes-database-migration/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-07-02/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Need to migrate from one database to another without downtime?</p>
<p>Dual writes are one approach that deserves more attention.</p>
<p>Most database migrations fall into one of a few buckets:</p>
<ul>
<li>Export and import</li>
<li>Replication between two databases</li>
<li>Services specifically built to synchronize data</li>
</ul>
<p>All of those approaches can work well.
But sometimes you need both databases active while gradually migrating traffic from one to the other.</p>
<p>That’s where dual writes become interesting.</p>
<h2>What Are Dual Writes?</h2>
<p>The idea is exactly what it sounds like.
Every write is sent to both databases.</p>
<p>Insert a row?
Write it twice.</p>
<p>Update a record?
Update it twice.</p>
<p>Delete something?
Yup, delete it twice.</p>
<p>The goal is to keep the old and new databases synchronized while both are active.</p>
<h2>How It Works</h2>
<p>The most common approach is implementing dual writes directly in the application.</p>
<p>Instead of maintaining one database connection pool, the application maintains two and executes write operations against both systems.</p>
<p>In some cases, infrastructure can help as well.</p>
<p>For example, Envoy supports request mirroring patterns that can be useful when migrating certain technologies, such as Redis.</p>
<p>The implementation will vary, but the concept remains the same: each write is performed twice.</p>
<h2>The Hard Part: Failure Handling</h2>
<p>The hard part is not writing twice, but rather handling partial success.</p>
<p>What happens when Database A succeeds, but Database B fails?</p>
<p>Now the two databases disagree.</p>
<p>Do you retry?
Can the operation be safely retried?</p>
<p>Can you roll back the successful write?</p>
<p>Do you have locking or reconciliation mechanisms?</p>
<p>This is where dual writes become significantly more complex than they initially sound.</p>
<p>Writing twice is easy.
Maintaining correctness is the hard part.</p>
<h2>Where Dual Writes Work Best</h2>
<p>Dual writes tend to work best when:</p>
<ul>
<li>Updates occur frequently</li>
<li>Eventual consistency is acceptable</li>
<li>Reconciliation processes exist</li>
<li>Future updates naturally correct drift</li>
</ul>
<p>They become much harder in systems that require strict consistency guarantees for every operation.</p>
<h2>Why Teams Use Them</h2>
<p>Despite the complexity, dual writes enable a powerful migration approach.</p>
<p>You can:</p>
<ul>
<li>Introduce a new database platform or table</li>
<li>Keep it synchronized with the old platform</li>
<li>Gradually migrate read traffic</li>
<li>Eventually retire the old database</li>
</ul>
<p>All without requiring a large downtime window.</p>
<h2>Final Thoughts</h2>
<p>Like most migration strategies, dual writes are a tradeoff.</p>
<p>They add complexity.</p>
<p>But they also enable something extremely valuable: migrating between database platforms while the system remains live.</p>
<p>When downtime is not an option, dual writes can be one of the most powerful migration techniques available.</p>
]]></description>
        <pubDate>Thu, 02 Jul 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-07-02-dual-writes-database-migration.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Glue Services: Part Two — Data Synchronization</title>
        <link>https://bencane.com/posts/2026-06-25-glue-services-data-synchronization/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-06-25/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Glue Services: Part Two — Data Synchronization.</p>
<p>I recently talked about using glue services (Anti-Corruption Layers) to isolate modern platforms from legacy integrations.</p>
<p>Today I want to talk about another type of glue service: data synchronization services.</p>
<h2>🗃️ The Real Modernization Problem</h2>
<p>One of the hardest parts of replacing a legacy platform is usually not the application itself.</p>
<p>It’s the data.</p>
<p>In a perfect world, you could: take downtime, export the database, import it into the new platform, and switch traffic.</p>
<p>Simple.</p>
<p>But large downtime windows are increasingly rare.
And big-bang migrations are risky enough that many organizations actively avoid them.</p>
<p>That means old and new systems often need to run side-by-side for a while.</p>
<p>Both platforms stay active.</p>
<p>Both serve customers.</p>
<p>Both need access to the same data.
This is where modernization becomes difficult.</p>
<h2>🔄 Keeping Two Systems in Sync</h2>
<p>When you are changing both the application and the underlying database, keeping data synchronized becomes difficult very quickly.</p>
<p>Especially when the two systems use different schemas, database technologies, data models, and update patterns.</p>
<p>You can’t simply export/import anymore.
You need continuous synchronization.</p>
<h2>👨🏻‍🤝‍👨🏾 Enter the Synchronization Service</h2>
<p>One useful pattern is to build a dedicated synchronization service whose sole responsibility is to keep data aligned across systems.</p>
<p>How synchronization works depends entirely on the platform.</p>
<p>I’ve used several approaches over the years.</p>
<h2>⚙️ Database Triggers</h2>
<p>One approach is to use database triggers or change-capture mechanisms in the legacy system.</p>
<p>When data changes, it’s detected, the synchronization service processes it, and the new platform is updated.</p>
<h2>📩 Event-Based Synchronization</h2>
<p>Another approach is to have the legacy platform emit events via a message broker.</p>
<p>The synchronization service consumes those events and updates the new database/platform accordingly.
This tends to work especially well when modernizing toward event-driven systems.</p>
<h2>⏳ Temporary Infrastructure</h2>
<p>The important thing to remember is that these synchronization services are usually temporary.
Their job is not to become a permanent platform.</p>
<p>Their purpose is to enable safe migration while reducing downtime and risk.</p>
<p>Once the migration is complete, the glue service disappears.</p>
<h2>⚠️ One Important Warning</h2>
<p>Bi-directional synchronization becomes extremely difficult very quickly.</p>
<p>With bi-directional synchronization, you have to solve:</p>
<ul>
<li>Conflict resolution</li>
<li>Consistency problems</li>
<li>Update ordering</li>
<li>Partial failures</li>
</ul>
<p>Whenever possible, avoid bi-directional synchronization.</p>
<p>In many cases, it is simpler to migrate the application layer first, then the database layer.</p>
<h2>🧐 Final Thought</h2>
<p>Modernization is rarely just replacing code.
A major challenge is safely bridging old and new systems during transition periods.</p>
<p>Synchronization glue services are often what make zero-downtime migrations possible.</p>
]]></description>
        <pubDate>Thu, 25 Jun 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-06-25-data-synchronization-glue-services.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>When modernizing legacy systems, don’t be afraid to build glue services</title>
        <link>https://bencane.com/posts/2026-06-18-glue-services-legacy-modernization/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-06-18/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>When modernizing legacy systems, don’t be afraid to build glue services.</p>
<p>One of the biggest mistakes I see during modernization efforts is letting legacy integrations dictate the design of the new platform.</p>
<p>That usually leads to putting fresh paint on the same old house.
Rebuilding the same architecture with a newer tech stack.</p>
<h2>😴 The Dream vs. Reality</h2>
<p>The dream project is building a brand-new platform with no existing users, integrations, or constraints.</p>
<p>You can design everything “the right way” from day one.
But most real-world projects are not like that.</p>
<p>Most projects are modernization efforts.
And most modernization efforts are weighed down by existing integrations, legacy protocols, and operational dependencies.</p>
<p>Changing customer behavior and expectations is often harder than rebuilding the platform itself.</p>
<h2>🥲 The Common Mistake</h2>
<p>Many teams respond to this by centering their new platform on the old integration model.</p>
<p>If customers use XML over Message Brokers, the new platform may speak JSON, but it still inherits the event-driven constraints—even when they no longer make sense.</p>
<p>If the legacy system exchanges files, the new platform is usually heavily batch-based.</p>
<p>The problem is:</p>
<p><strong>Your modernization effort becomes constrained by the past.</strong></p>
<h2>🤯 A Better Approach: Glue Services</h2>
<p>The formal term for this pattern is an <em>Anti-Corruption Layer</em>.</p>
<p>Personally, I think “glue service” explains it better.
It makes the concept easier to understand.</p>
<p>Build the internal platform the way you actually want it designed.</p>
<p>Then build lightweight edge services that translate between legacy integrations and your modern platform.</p>
<ul>
<li>XML over Message Brokers? Use <code>gRPC</code> internally.</li>
<li>Files? Break them into APIs.</li>
<li>Long-lived <code>ISO8583</code> TCP connections? Terminate them at the edge and use <code>gRPC</code> + microservices behind them.</li>
</ul>
<p>The glue service has one responsibility: to translate between old and new worlds.</p>
<h2>🤔 Why This Matters</h2>
<p>These glue services give you two major advantages.</p>
<p>First, your internal architecture stays modern and optimized for current engineering practices.
Second, your customers and integrations do not need to migrate immediately.</p>
<p>That dramatically reduces modernization risk.
Temporary glue services can be huge modernization enablers.</p>
<p>But sometimes these glue services live forever, which is ok.</p>
<p>The important thing is that legacy integrations no longer constrain your modern platform.</p>
<h2>🔁 It Works at Both Ends</h2>
<p>This pattern applies both to inbound (clients calling your platform) and outbound (your platform calling legacy systems) integrations.</p>
<p>In many large systems, you’ll find glue services on both sides of the platform.</p>
<p>At the edge entering the system, and again leaving it.</p>
<h2>🧐 Final Thoughts</h2>
<p>Modernization is rarely just rewriting code.
A major challenge is safely bridging old and new systems during transition periods.</p>
<p>I’ve used this pattern many times.</p>
<ul>
<li>Breaking files into APIs</li>
<li>Translating <code>ISO8583</code> and <code>ISO20022</code> messages into Protobuf</li>
<li>Terminating long-lived TCP sessions at the edge of a <code>gRPC</code>-based microservices platform</li>
</ul>
<p>Glue services may not be glamorous, but they are one of the safest ways to modernize systems without dragging the past into the future.</p>
]]></description>
        <pubDate>Thu, 18 Jun 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-06-18-glue-services.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Coding agents can’t see your architecture diagrams—fix that</title>
        <link>https://bencane.com/posts/2026-06-11-code-based-architecture-diagrams/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-06-11/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Coding agents can’t see your architecture diagrams—fix that.</p>
<p>I’ve been talking a lot about architecture documentation and how it helps both humans and agents.</p>
<p>From my experience, most teams spend 90% of their effort on diagrams and 10% on text.</p>
<p>Why? Because they are the fastest way to communicate a system:</p>
<p>Diagrams show how components interact, how data flows, and what depends on what.
They help humans understand complex systems quickly.</p>
<p>That’s why most architecture documentation leans heavily on diagrams.</p>
<h2>🤔 The Problem</h2>
<p>Most diagrams are images.
Images are great for humans, but not for agents.</p>
<p>Some agents can interpret images, but not reliably or consistently.
And even when an agent can interpret an image, it can’t reliably reason about it or keep it up to date.</p>
<h2>🧠 Make Diagrams Understandable</h2>
<p>You don’t need to move away from diagrams to embrace agents.
Just make them understandable to agents.</p>
<p>If diagrams are the most valuable part of your architecture documentation, make them readable as code.</p>
<h2>📝 Use Code-Based Diagrams</h2>
<p>Tools like Mermaid turn text into diagrams.</p>
<p>That means agents can read them, reason about them, and even keep them up to date.</p>
<p>A Mermaid diagram isn’t just documentation—it’s structured context.</p>
<h2>💡 Why This Matters</h2>
<p>When your diagrams are code:</p>
<ul>
<li>They live with your system</li>
<li>They evolve with changes (as long as you update them)</li>
<li>They can be versioned</li>
<li>They can be generated or modified by agents</li>
</ul>
<p>And most importantly, they become a living, usable context.</p>
<h2>🔄 Keep Them Up to Date</h2>
<p>Keeping architecture documentation up to date is always a pain.
Images make that even harder.</p>
<p>Code-based diagrams are much easier to keep up to date.
Especially when you use agents and the architecture sits next to your code.</p>
<p>When you make a change, direct the agent to update the architecture documentation as well.
Even if you don’t fully trust agents with code changes, architecture documentation is low risk.</p>
<p>Instead of letting it drift over time, let agents keep it up to date.</p>
]]></description>
        <pubDate>Thu, 11 Jun 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-06-11-code-based-diagrams.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Most teams put low-level architecture in the wrong place</title>
        <link>https://bencane.com/posts/2026-06-04-component-architecture-in-codebase/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-06-04/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Most teams put low-level architecture in the wrong place—if they document it at all.</p>
<p>I recently wrote about keeping architecture documentation in Git and making it available to coding agents.</p>
<p>A common pattern I see is teams maintaining a central architecture repository that spans multiple services and platforms.
It’s an approach I use as well—but there’s a nuance.</p>
<h2>🏯 Architecture Has Layers</h2>
<p>Good architecture documentation spans multiple levels.</p>
<p>At a high level, you have:</p>
<p><strong>Business Architecture</strong> 👨‍💼</p>
<p>Capabilities, domains, and high-level concepts.</p>
<p><strong>Solution Architecture</strong> 👩‍🎨</p>
<p>Mapping capabilities to systems.</p>
<p>Defining which systems own what.</p>
<p>What gets replaced.</p>
<p>What gets invested in.</p>
<p><strong>System Architecture</strong> 👷</p>
<p>Services, databases, jobs, infrastructure, and how they interact.</p>
<p>These three layers belong in a central architecture repository.</p>
<p>But there is one more layer that many teams forget.</p>
<h2>🔍 The Missing Layer</h2>
<p><strong>Component Architecture</strong> 🧱</p>
<p>This is where architecture stops being abstract and starts becoming actionable.</p>
<p>Things like:</p>
<ul>
<li>Why middleware X was chosen over Y</li>
<li>How a request flows through a handler</li>
<li>What interfaces exist and why</li>
<li>What metrics and tracing are required</li>
<li>How a feature is expected to behave</li>
</ul>
<p>These decisions are too detailed for a central architecture repo—and too important to leave undocumented.</p>
<h2>📦 Put It in the Codebase</h2>
<p>Component-level architecture belongs with the code.</p>
<p>Not in a separate repo, not in a wiki, and certainly not only in tribal knowledge.</p>
<p>Why does this matter?
Because this is where architecture stops being abstract and becomes a specification.</p>
<p>A specification for engineers, reviewers, and now, coding agents.</p>
<h2>🤖 Why This Matters for Agents</h2>
<p>Agents don’t understand your system by osmosis the way human engineers do.</p>
<p>They only know what you give them.
If your architecture decisions live far away from your code, your agent:</p>
<ul>
<li>Misses constraints: Uses technologies that aren’t allowed in your environment</li>
<li>Guesses at design: Assumes <code>REST</code> when you expect event-driven messaging</li>
<li>Makes inconsistent choices: Pulls in the wrong libraries or dependencies</li>
</ul>
<p>But when that context lives in the repo, architecture:</p>
<ul>
<li>Evolves with the code</li>
<li>Is versioned</li>
<li>Is available at the moment it’s needed</li>
</ul>
<h2>🧠 Final Thought</h2>
<p>I’ve always believed the best engineers don’t just understand one codebase; they learn the end-to-end system.</p>
<p>The same applies to agents.</p>
<p>If you want better output from an agent, give it the same level of detail you’d give a senior engineer.</p>
]]></description>
        <pubDate>Thu, 04 Jun 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-06-04-component-architecture.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Your coding agent is missing one thing: architectural context</title>
        <link>https://bencane.com/posts/2026-05-28-coding-agent-architectural-context/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-05-28/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Your coding agent is missing one thing: architectural context.</p>
<p>I’ve been a big believer in Architecture Decision Records (<code>ADRs</code>) long before coding agents came along.</p>
<p>Documenting decisions gives engineers context:</p>
<p>Why is the system designed a certain way?
What constraints existed at the time?
What tradeoffs were made?</p>
<p>That context matters.
It also matters for agents.</p>
<h2>🤖 Agents Need Context Too</h2>
<p>Unlike human engineers, agents don’t get context from hallway conversations, shadowing others, or tribal knowledge.</p>
<p>They only know what you capture.
The best way to capture architectural context?
Write it down as a decision record—and make it accessible to agents.</p>
<p>The only question is, what’s the best way to make decision records accessible?</p>
<h2>🏗️ Option 1: MCP Server</h2>
<p>If your <code>ADRs</code> live in a wiki or documentation system, you can expose them through an <code>MCP</code> server.</p>
<p>This works well when documentation is spread across teams or multiple systems that need to be aggregated.</p>
<p>You want a unified interface for agents.
<code>MCP</code> is a good approach, but it comes with some infrastructure overhead.</p>
<h2>🧱 Option 2: Keep ADRs in Git</h2>
<p>I’ve long preferred storing <code>ADRs</code> in Git.</p>
<p>It provides versioning, review workflows, automated validation, and is where engineering work happens.
Storing <code>ADRs</code> in Git, ideally alongside your code, is the fastest way to give agents usable context.</p>
<p>The challenge is that architecture often spans multiple services and repositories.
So many centralize their architecture into a single repository, which is not where your code lives.</p>
<h2>🌉 Bridging the Gap</h2>
<p>Most modern coding agents let you include additional directories or sources at runtime, either through slash commands or CLI options.</p>
<p>That means you can: open your codebase, include your architecture repository, and run the agent with context.</p>
<p>Just adding another directory gives your agent an understanding of system constraints, architecture decisions, technology choices, and surrounding systems.
These are not things an agent can reliably infer from code alone.</p>
<h2>💡 Final Thought: Why Context Matters</h2>
<p>With architectural context, agents produce code that aligns with your system.</p>
<p>When engineers understand the system end to end, they make better decisions.
The same applies to agents.</p>
<p>If you want better results, give better context.</p>
]]></description>
        <pubDate>Thu, 28 May 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-05-28-architectural-context.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Health-check the listener your gRPC traffic actually uses</title>
        <link>https://bencane.com/posts/2026-05-21-grpc-health-check-listener/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-05-21/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>One of the easiest ways to break a <code>gRPC</code> service in production is health-checking the wrong listener.</p>
<p>A common issue I see teams run into when adopting <code>gRPC</code> is leaving readiness checks pointed at their <code>HTTP</code> listener while production traffic actually flows through <code>gRPC</code>.</p>
<p>Everything looks fine until it suddenly doesn’t.</p>
<h2>🤔 The Problem</h2>
<p>Many <code>gRPC</code> services run two listeners: one for <code>HTTP</code> and one for <code>gRPC</code>.</p>
<p>The <code>HTTP</code> listener often exists for metrics, liveness checks, and management APIs.
Teams moving to <code>gRPC</code> often reuse the <code>HTTP</code> health checks they set up for their REST-based services.</p>
<p>It’s generally a good idea to reuse what you already have, but in this case, it can be misleading.</p>
<h2>⚠️ Health-Check What Serves Traffic</h2>
<p>If customers connect through <code>gRPC</code>, your first readiness check should too.</p>
<p>Your <code>HTTP</code> listener can be perfectly healthy while the <code>gRPC</code> listener is misconfigured, hung, or otherwise failing.</p>
<p>Meanwhile, Kubernetes, load balancers, and dashboards might all show green. ✅</p>
<p>This happens more often than people think.</p>
<h2>🩺 Better Ways to Monitor gRPC</h2>
<p>There are better ways to monitor your <code>gRPC</code> service.</p>
<h3>gRPC Health Probe ✅</h3>
<p>Use a real <code>gRPC</code> health check request against the listener.</p>
<p>This validates the actual serving path and confirms the service can respond over <code>gRPC</code>.</p>
<p>A strong default option.</p>
<h3>Build a Status gRPC Service 📋</h3>
<p>Expose an internal status method in your <code>gRPC</code> API.</p>
<p>This gives you flexibility to check deeper dependencies, such as database readiness, downstream systems, internal state, and maintenance toggles.</p>
<p>It’s more work, but more control.</p>
<h3>Use a Single Shared Listener ☝️</h3>
<p>Because <code>gRPC</code> runs on top of <code>HTTP/2</code>, many languages and frameworks can serve <code>HTTP</code> and <code>gRPC</code> traffic on the same listener.</p>
<p>That means an <code>HTTP</code> health endpoint may be acceptable because it checks the same network path.
It still does not fully validate <code>gRPC</code> behavior, but it is better than checking an entirely separate listener.</p>
<h2>🧠 Final Thoughts</h2>
<p><code>gRPC</code> is awesome.</p>
<p>But making a service production-ready means revisiting configurations inherited from REST services.</p>
<ul>
<li>Health checks</li>
<li>Load balancing behavior</li>
<li>Connection management</li>
<li>Contracts</li>
<li>Operational tooling</li>
</ul>
<p>None of these changes are difficult. They’re just easy to miss.</p>
]]></description>
        <pubDate>Thu, 21 May 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-05-21-grpc-health-checks.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Weighted load balancing has saved me more times than I can count</title>
        <link>https://bencane.com/posts/2026-05-14-weighted-load-balancing/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-05-14/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Weighted load balancing has saved me more times than I can count.</p>
<p>Many engineers think of load balancers as simple traffic distributors.</p>
<p>Send requests across servers.
Keep systems available.
Move on.</p>
<p>But one of their most valuable capabilities is often overlooked.
Weighted load balancing.</p>
<h2>🤨 What Is Weighted Load Balancing?</h2>
<p>From enterprise hardware appliances to software load balancers like HAProxy and Envoy Proxy, nearly all modern load balancers support some form of weighted routing.</p>
<p>Start with a standard balancing algorithm, such as round-robin.
Then apply weights so some targets receive more traffic than others.</p>
<p>For example, if two targets are weighted at 90 and 10, roughly 90% of traffic goes to one target and 10% to the other.
If targets have equal weights, traffic is typically distributed evenly.</p>
<p>Simple idea, critical feature.</p>
<h2>🤔 Why It Matters</h2>
<p>Weighted load balancing turns migrations from risky big-bang cutovers into small, adjustable dials.</p>
<p>Instead of flipping traffic all at once, you can gradually shift production traffic while observing behavior in real time.</p>
<p>That means a smaller blast radius, easier rollbacks, and safer production migrations.</p>
<h2>🧰 What I Actually Use It For</h2>
<p>I’ve rarely used weighted load balancing because one server had more capacity than another.</p>
<p>What I’ve used it for repeatedly is change management.</p>
<p>Ten years ago, to migrate from a legacy file transfer platform to newer platforms.
We used weighted load balancing to introduce the new platform gradually.</p>
<p>Six years ago, to control which transactions were routed to our old card payments platform versus the new platform, we introduced weighted load balancing in our global transaction router.</p>
<p>Last night, to run a canary deployment, we adjusted our service mesh using weighted routing via <code>xDS</code>.</p>
<p>Different eras, different platforms, same core concept.</p>
<h2>🕰️ Standing the Test of Time</h2>
<p>Weighted load balancing is not new.
It has existed for a long time.</p>
<p>Foundational patterns often become the enablers for newer platform practices.
Canary releases, blue/green deployments, service mesh traffic shifting.</p>
<p>Many of those ideas rely on the same underlying capability: <strong>controlling traffic through percentages and weighting.</strong></p>
<p>Good patterns tend to survive generations of technology.</p>
<h2>🧠 Final Thoughts</h2>
<p>Many software engineers will never build a load balancer.
They will never configure one themselves.</p>
<p>But understanding what these systems can do is still an advantage.</p>
<p>Because migrations are often less about code and more about traffic control.</p>
<p>Writing good software isn’t enough.
Knowing when and how to shift 1% of traffic can make or break a migration.</p>
]]></description>
        <pubDate>Thu, 14 May 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-05-14-weighted-load-balancing.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>YOLO Is a Terrible Strategy for Validating Production Changes</title>
        <link>https://bencane.com/posts/2026-05-07-yolo-production-validation/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-05-07/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>YOLO is a terrible strategy for validating production changes.</p>
<p>How many times have you seen it?</p>
<p>Your platform is running smoothly.
No alerts, no issues.
Then suddenly, something breaks.</p>
<p>After digging in, you discover the cause: another system you depend on made a change, and that change broke your platform.</p>
<p>They didn’t notice it broke. You did, much too late…</p>
<p>How many times have you been the cause of another platform breaking?</p>
<h2>🥶 Cold Reality</h2>
<p>I wish the above scenario were rare, but it happens constantly across the technology industry.</p>
<p>It happens between internal teams, third-party integrations, and shared infrastructure teams.</p>
<p>These scenarios make you wonder, “How was that change validated?”</p>
<p>Maybe they tested it, and their validation had gaps.
Maybe they did little validation at all.
If any.</p>
<p>Either way, the result is the same: <strong>they validated their change with 100% of production traffic.</strong>
Bad plan.</p>
<h2>💡 Better Ways to Validate Changes</h2>
<p>There are many ways teams can reduce production risk when rolling out changes, and the best teams combine the following approaches.</p>
<h3>Canary Releases 🐤</h3>
<p>I talk about canary deployments often.</p>
<p>Instead of moving 100% of traffic at once, move small percentages gradually and observe behavior closely.</p>
<p><strong>That observed part matters.</strong>
Look at error rates, latency changes (beyond normal platform warmup), resource spikes, and unexpected retries.
All of these indicate customer impact.</p>
<p>Canary deployments are one of the best ways to reduce the blast radius of changes, identify problems quickly, and self-correct.</p>
<h3>Shadow Traffic 🪞</h3>
<p>Traffic mirroring sends production traffic to a new version before routing live traffic there.</p>
<p>Responses are ignored, but you observe behavior and monitor the same signals you would with a canary release without sacrificing a customer request.</p>
<h3>Synthetic Traffic 🤖</h3>
<p>Synthetic traffic simulates user behavior continuously.
It’s great for monitoring customer experience, but also a great way to validate new deployments.</p>
<p>Route synthetic traffic to upgraded instances first and verify behavior before moving real traffic.
If it fails with synthetic traffic, it likely won’t survive real traffic.</p>
<h3>Smoke Tests 😶‍🌫️</h3>
<p>The classic approach.
After deployment, run a small set of fast tests to confirm the platform is fundamentally working.</p>
<p>Smoke tests don’t need to be fancy; they can be shell scripts, API calls, read-only requests, a test file, or full end-to-end validation.</p>
<p>Their purpose is simple: to quickly catch obvious breakage.</p>
<h2>🧠 Final Thoughts</h2>
<p>Don’t think of the above methods as mutually exclusive choices.
Combine them.</p>
<p>Some platforms I work on combine canary releases, shadow traffic, and synthetic traffic.
Others use smoke tests plus canary releases.</p>
<p>The more layers of validation you have, the more likely you are to catch issues before your customers do.
Because having your customers validate changes for you is a poor strategy.</p>
]]></description>
        <pubDate>Thu, 07 May 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-05-07-production-validation.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Deterministic routing is one of the most effective ways distributed systems reduce consistency problems at scale</title>
        <link>https://bencane.com/posts/2026-04-30-deterministic-routing/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-04-30/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Deterministic routing is one of the most effective ways distributed systems reduce consistency problems at scale.</p>
<p>It is a foundational technique used by many modern databases, caches, and large-scale platforms.
Understand how it works and you can apply the same pattern in your own systems.</p>
<h2>🤔 Understanding the Problem</h2>
<p>At some point, every successful system hits the limits of a single database instance.</p>
<p>A single server can only handle so many connections, queries, writes, storage capacity, or CPU/memory demands.
Even with the best hardware, performance eventually degrades.
So systems scale horizontally.</p>
<p>Instead of sending all traffic to a single database server, requests are distributed across multiple nodes.</p>
<p>At the same time, resiliency matters.
If one server fails and all data resides there, the outage can be severe.</p>
<p>So modern databases spread data across multiple nodes, availability zones, and regions.</p>
<p>Distributing load and data solves both capacity and resiliency problems.
But it introduces another challenge.</p>
<p>How do you keep request behavior consistent when data is distributed across multiple systems?</p>
<h2>⚠️ Why Replication Is Not Enough</h2>
<p>Replication helps, but it does not solve every consistency problem.</p>
<p>Imagine a write lands on Server 1.
Immediately after, a read request for the same data lands on Server 67.
Will Server 67 have the latest version?
Maybe, but often not.</p>
<h3>Asynchronous Replication</h3>
<p>With asynchronous replication, Server 1 will accept the write and replicate the data to other servers in the background.
That means a follow-up read on any other node may return stale data.</p>
<h3>Synchronous Replication</h3>
<p>With synchronous replication, the write on Server 1 will wait for an acknowledgment from all replicas before returning a success.
While this improves consistency guarantees, it increases latency.</p>
<p>The farther apart a replica is, the worse this gets.
Local writes may be fast, but cross-region writes will be slow.
Plus, is it really feasible to replicate data across every single node?</p>
<p>So the question becomes: <em>How do you preserve consistency, without paying latency taxes?</em></p>
<h2>🔀 Route Requests to the Data</h2>
<p>A highly effective answer is deterministic routing.</p>
<p>Instead of moving data to where requests might land, move requests to where the data already exists.</p>
<p>If requests for the same key can go to the same node, you gain predictable ownership, reduced stale reads, lower coordination overhead, and easier horizontal scaling.</p>
<h2>👨‍🏫 How Deterministic Routing Works</h2>
<p>At a high level, the system needs a repeatable way to decide where requests should go.</p>
<p>A common approach is hashing.</p>
<ul>
<li>A hash of <code>user123</code> always goes to Node 7</li>
<li>A hash of <code>user456</code> always goes to Node 42</li>
</ul>
<p>As long as the same key produces the same result, requests can be consistently routed to the same owner.
Many modern databases implement deterministic routing through techniques like consistent hashing, partition maps, and shard ranges.</p>
<h2>🗺️ Where Routing Logic Lives</h2>
<p>Different systems solve routing in different places.</p>
<h3>Client-side Routing</h3>
<p>The client library knows the partition map and sends requests directly to the correct node.
Used by many distributed caches and databases.</p>
<h3>Proxy / Router Tier</h3>
<p>A small router sits in front of nodes and forwards traffic appropriately.
Useful when client behavior cannot be influenced.</p>
<h3>Server-side Forwarding</h3>
<p>Requests land anywhere, and the receiving node forwards internally to the owning node.
Simple for clients, doesn’t introduce a proxy failure point, but introduces complex cluster discovery/health monitoring.</p>
<p>Each model has tradeoffs.</p>
<h2>🧰 Routing Does Not Replace Replication</h2>
<p>Deterministic routing is powerful, but not magic.
What happens when the owning node is down?
You still need replication.</p>
<p>Modern databases combine both: deterministic routing for performance and ownership, plus replication for durability and failover.</p>
<h2>🧠 Why This Matters Beyond Databases</h2>
<p>Distributed databases use this approach, but it is not unique to them.</p>
<p>Deterministic routing can be used to solve: session ownership, user affinity, in-memory workflow coordination, work queue partitioning, and more.</p>
<p>I’ve used deterministic routing many times to solve load distribution and consistency problems.</p>
<p>At scale, the answer is not always more/better hardware.
Consistency and availability problems are not always solved with replication alone.</p>
<p>Sometimes the best answer is simply to send the request to the right place.</p>
]]></description>
        <pubDate>Thu, 30 Apr 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-04-30-deterministic-routing.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>When you think of microservices, you probably think of centralized shared services. But there&#39;s another valid pattern that is rarely discussed</title>
        <link>https://bencane.com/posts/2026-04-23-microservices-local-platform-pattern/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-04-23/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>When you think of microservices, you probably think of centralized shared services.
But there’s another valid pattern that is rarely discussed: running the same microservice inside multiple platforms.</p>
<h2>🧩 How It Usually Works</h2>
<p>Most microservice designs follow the same model:</p>
<ul>
<li>Break systems into capabilities, teams, or functions</li>
<li>Deploy one shared service for each capability</li>
<li>Any platform that needs it calls that centralized service</li>
</ul>
<p>That works well for many cases, but it’s not the only model.</p>
<h2>🏗️ How We Got Here</h2>
<p>Before microservices, many organizations used Service-Oriented Architecture (SOA).</p>
<p>Despite being labeled as antiquated, SOA and microservices are not that different.
Both break down systems into capabilities that communicate with each other.
The biggest difference is scope.</p>
<p>In SOA, a “Payments Service” might own:</p>
<ul>
<li>Message parsing</li>
<li>Validation</li>
<li>Balance checks</li>
<li>Currency conversion</li>
<li>Settlement logic</li>
</ul>
<p>While other SOA services would own “Users” or “Accounting”.
Today, that payment service would be considered an entire platform, with each of those capabilities implemented as microservices within that domain.</p>
<p>Microservices are often the same idea as SOA, just at a more granular level.</p>
<h2>🎯 Why Centralization Became the Default</h2>
<p>One reason microservices gained traction was the need to avoid duplication.
Capabilities were often rebuilt across multiple systems.
For example, Currency Conversion is needed in Payments, Accounting, and many other platforms.</p>
<p>Duplication is not just wasteful, it creates real problems: logic drift, coordination overhead, and inconsistent outcomes across systems.
Packaging that capability as a standalone service solved real problems: build once, reuse everywhere.</p>
<h2>⚠️ The Downside of Centralization</h2>
<p>In cell-based architectures, platforms are usually designed to be self-contained and failure-isolated.
That means a mission-critical platform depending on a centralized service shared by other platforms can become a design smell.</p>
<ul>
<li>Cross-cell dependencies</li>
<li>Added latency</li>
<li>Shared failure domains</li>
<li>Complex failover scenarios</li>
</ul>
<p>So teams, once again, solve these problems by rebuilding the same capability locally.</p>
<h2>🔁 Another Option</h2>
<p>Instead of rebuilding the capability each time, deploy the same microservice codebase inside multiple platforms.
If both Payments and Accounting need a currency conversion service, deploy the same service within each platform.</p>
<p>It’s the same codebase and capability, but with local ownership and resilience.
You get reuse without forced centralization.</p>
<h2>🧪 Caveats from Experience</h2>
<p>This pattern works when applied carefully.</p>
<h3>1️⃣ Strong Ownership</h3>
<p>A shared codebase still needs a clear owning team.
Others can contribute, but someone must own quality, roadmap, and releases.</p>
<h3>2️⃣ Pick the Right Capabilities</h3>
<p>Not everything is a great fit.
Something like currency conversion is well-scoped, relatively stateless, and doesn’t have unique business logic based on which platform is calling it.
It’s a strong example.</p>
<p>But other services that have unique logic for each platform domain or require consistency across different platforms are less of a fit.</p>
<h3>3️⃣ Operational Discipline</h3>
<p>Using the same codebase doesn’t automatically solve all problems; you can still run into drift across platforms if each is running a different version.
Changes in behavior still sometimes need coordination.</p>
<p>But with a single codebase, these issues are far easier to address.</p>
<h2>💭 Final Thoughts</h2>
<p>Microservices gave us reusable building blocks.
Sometimes the best use of a microservice is not one centralized deployment.
Sometimes it’s many local deployments of the same capability.</p>
<p>Just reuse the software while maintaining autonomy.</p>
]]></description>
        <pubDate>Thu, 23 Apr 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-04-23-microservice-pattern.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Are you using traffic mirroring in production? If not, try it out.</title>
        <link>https://bencane.com/posts/2026-04-16-traffic-mirroring-in-production/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-04-16/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Are you using traffic mirroring in production?
If not, you might be missing one of the safest ways to test and observe production changes.</p>
<h2>🚦 What is Traffic Mirroring?</h2>
<p>Traffic mirroring in <code>Istio</code> or <code>Envoy Proxy</code> lets you send a copy of live traffic to a secondary target.</p>
<p>When enabled, traffic to <code>/service</code> routes to <code>cluster1</code> as normal, and a mirrored copy is sent to <code>cluster2</code>.</p>
<p><strong>The key:</strong> mirrored traffic is fire-and-forget.
Responses are ignored and never impact the primary request.</p>
<h2>🧪 Why It’s Powerful</h2>
<h3>1️⃣ Shadow Traffic for Safe Testing</h3>
<p>The most common use case is shadow traffic.</p>
<p>When migrating platforms or deploying a new version of an application, you can send real traffic to the new system, observe behavior, and validate responses.</p>
<p>All without impacting users. No risky cutovers.
You see exactly how the new system behaves under real load.</p>
<h3>2️⃣ Out-of-Band Traffic Inspection</h3>
<p>Another powerful use case is traffic inspection.</p>
<p>Inline inspection is risky.
It adds latency, introduces new failure points, and becomes part of the critical path.</p>
<p>With traffic mirroring, you can inspect traffic, analyze requests, and detect anomalies.</p>
<p>All without impacting the primary path.</p>
<h2>😶‍🌫️ Reality Check</h2>
<p>It’s not perfect.
There is some overhead.</p>
<p>Mirroring adds load to the sidecar, which may or may not be acceptable for your system.
In my experience, it’s negligible, but it’s something you should measure in your own environment before deploying to production.</p>
<h2>🧠 Final Thoughts</h2>
<p>Traffic mirroring is one of the safest ways to validate migrations, test new systems, and observe real production behavior.</p>
<p>The hard part isn’t mirroring traffic.
It’s running two production systems in parallel.
That’s the real cost, and the real tradeoff.</p>
<p>But if you can afford that cost, traffic mirroring is an incredibly powerful tool.</p>
<p>If you want to dig deeper:</p>
<ul>
<li><a href="https://istio.io/latest/docs/tasks/traffic-management/mirroring/">Istio traffic mirroring docs</a> explain the workflow.</li>
<li><a href="https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-routeaction-requestmirrorpolicy">Envoy request mirror policy docs</a> cover the lower-level routing behavior.</li>
</ul>
]]></description>
        <pubDate>Thu, 16 Apr 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-04-16-traffic-mirroring.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Agent Skills Are Becoming the Best Way to Capture Institutional Knowledge</title>
        <link>https://bencane.com/posts/2026-04-09-agent-skills-institutional-knowledge/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-04-09/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Use Agent Skills to capture institutional knowledge and make it usable by coding agents.</p>
<p>Every organization has institutional knowledge.</p>
<ul>
<li>Internal frameworks</li>
<li>Preferred practices</li>
<li>Platform-specific capabilities</li>
</ul>
<p>It exists everywhere.
But it’s often undocumented… or buried in a wiki no one reads.</p>
<p>As coding agents take on more work, this problem gets worse.</p>
<p>If you ask an agent to build a new service, you want it to use your internal framework, follow your patterns, and respect your organizational constraints.</p>
<p>A human engineer would ask questions.
An agent won’t, unless you give it that context.</p>
<h2>📚 Agent Skills as Knowledge Distribution</h2>
<p>Most people think about Agent Skills as actions:</p>
<ul>
<li>Convert markdown to PDF</li>
<li>Review this pull request</li>
<li>Commit my changes</li>
</ul>
<p>But the more interesting use case is guidance.</p>
<p>Skills aren’t just for doing things.
They’re for shaping agent output.</p>
<p>Agents discover and use skills based on intent.</p>
<p>If a user asks: “Create a new Python service.”</p>
<p>The agent looks for relevant skills:</p>
<ul>
<li>Language conventions (PEP 8, etc.)</li>
<li>Internal frameworks</li>
<li>Organizational standards</li>
</ul>
<p>That’s where institutional knowledge belongs.</p>
<p>Instead of hoping engineers remember to tell the agent:</p>
<ul>
<li>“We use Flask, not Django.”</li>
<li>“Stick to the standard library.”</li>
<li>“Follow this service layout.”</li>
</ul>
<p>You capture that into a skill.
The agent applies it automatically.</p>
<h2>🧠 Why This Matters</h2>
<p>Institutional knowledge only works if it's:</p>
<ul>
<li>Discoverable</li>
<li>Applied consistently</li>
</ul>
<p>Agent Skills give you both.</p>
<p>They turn tribal knowledge into something agents can find, understand, and use.</p>
<h2>⚠️ The Tradeoff (For Now)</h2>
<p>Right now, this introduces duplication.</p>
<p>Most teams already have internal docs, style guides, &amp; wikis.</p>
<p>And now you’re putting the same information into skills.
Which feels like extra work.</p>
<p>But it poses an interesting question:</p>
<p>As agents become the primary interface…
Will engineers read the wiki? Or ask the agent?</p>
<h2>🧠 Final Thoughts</h2>
<p>As agents take on more of the implementation work, where you store knowledge becomes more important.
Making that knowledge accessible to agents becomes essential.</p>
<p>Agent Skills aren’t just automation tools.</p>
<p>They are becoming the interface for standards, practices, and institutional knowledge.</p>
<p>And teams that embrace that early will see more consistent output from both humans and agents.</p>
]]></description>
        <pubDate>Thu, 09 Apr 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-04-09-institutional-knowledge.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Saved Prompts Are Dead. Agent Skills Are the Future.</title>
        <link>https://bencane.com/posts/2026-04-02-saved-prompts-are-dead-agent-skills/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-04-02/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Saved prompts are dead.
Agent Skills are the next step.</p>
<p>If you’ve been around for a while, you probably have a file full of bash one-liners.</p>
<p>Small scripts or commands you saved because they solved a problem you didn’t want to automate properly.</p>
<p>When coding agents arrived, prompts became the new one-liners.</p>
<p>Useful prompts were saved, reused, and eventually turned into “prompt files”, then slash commands like <code>/do-something</code>.</p>
<p>But that model has already evolved.</p>
<h2>⚙️ Agent Skills</h2>
<p>Agent Skills are the next iteration.</p>
<p>At a basic level, a skill looks a lot like a saved prompt: a directory with a markdown file.</p>
<p>What makes it different is how it’s used.</p>
<p>Skills include metadata like name and description, allowing agents to discover them.</p>
<p>Instead of explicitly calling a prompt every time, the agent can determine when to use a skill based on intent.</p>
<p>This is referred to as progressive disclosure:</p>
<ul>
<li>Agent loads skill metadata</li>
<li>Matches it to your task</li>
<li>Then loads and executes the full skill when needed</li>
</ul>
<p>You can still call skills directly (<code>/</code>, <code>$</code>, <code>@</code>), but you don’t always have to.</p>
<h2>🧠 More Than Just Prompts</h2>
<p>The real differentiator is that skills aren’t just prompts.</p>
<p>They can include reference documentation, templates, and scripts.</p>
<p>This means you’re no longer just telling the agent what to do.</p>
<p>You’re giving it tools and context to execute and validate tasks.</p>
<p>For more complex workflows, it’s often easier to write a script and teach the agent how to use it than to encode everything in a prompt.</p>
<h2>⚠️ A Word of Caution</h2>
<p>This power comes with risk.</p>
<p>Skills can include executable logic and tell agents to perform tasks.</p>
<p>That means a shared skill can contain malicious or unsafe behavior.</p>
<p>Treat them like any script you install:</p>
<ul>
<li>Understand what they do</li>
<li>Know where they come from</li>
<li>Review before using (watch out for hidden text or obfuscated instructions)</li>
</ul>
<h2>🧠 Final Thoughts</h2>
<p>Agent skills are a meaningful step forward.</p>
<p>They let you codify workflows, preferences, and repeatable agent tasks in a way that agents can discover.</p>
<p>They’re a strong productivity accelerator and a powerful way to capture institutional knowledge in a form agents can actually use.</p>
<p>(More on that in the next post.)</p>
]]></description>
        <pubDate>Thu, 02 Apr 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-04-02-agent-skills.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>Generating Code Faster Is Only Valuable If You Can Validate Every Change With Confidence</title>
        <link>https://bencane.com/posts/2026-03-26-validate-changes-with-confidence/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-03-26/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>Generating code faster is only valuable if you can validate every change with confidence.</p>
<p>Software engineering has never really been about writing code.
Coding is often the easy part.</p>
<p>Testing is harder, and many teams struggle with it.</p>
<p>As tools make it easier to generate code quickly, that gap widens.
If you can produce changes faster than you can validate them, you eventually create more code than you can safely operate.</p>
<p>Which begs the question: What does good testing actually look like?</p>
<h2>🔍 What Good Looks Like</h2>
<p>One of the biggest challenges I see is that teams struggle to understand what “good” testing means and never define it.</p>
<p>Pipelines are often built early in a project, when the team is small, and they rarely keep pace with the system and organization as they grow.</p>
<p>My starting principle is simple:</p>
<ul>
<li>
<p>At pull request time, you should have strong confidence that the change will not break the service or platform being modified.</p>
</li>
<li>
<p>Within a day of merging, you should have strong confidence that the change hasn’t broken the full customer journey that the platform supports.</p>
</li>
</ul>
<h2>🔁 On Pull Request</h2>
<p>For backend platforms, I like to see three levels of automated testing before merging.</p>
<h3>Code Tests (Unit Tests)</h3>
<p>This level is the foundation. Unit tests validate internal logic, error handling, and edge cases.
Techniques such as fuzz testing and benchmarking also reveal issues early.
As the test pyramid tells us, this is where the majority of testing and logic validation should take place.</p>
<h3>Service-Level Functional Tests</h3>
<p>Too many teams stop at unit tests for pull requests.
Functional tests should also be run in CI for every pull request.</p>
<p>Services should be tested in isolation with functional tests.
Dependencies can be mocked, but things like databases should ideally run for real (Dockerized).</p>
<p>This is where API contracts are validated and regressions can be identified without wondering whether the issue came from this change or another service.</p>
<h3>Platform-Level Functional Tests</h3>
<p>Testing a service alone isn’t enough.
Changes can break upstream or downstream dependencies.
Platform-level tests spin up the entire platform in CI and validate that services interact correctly.</p>
<p>These tests ensure the platform continues to work as a system.</p>
<p>For platforms with strict latency or resiliency requirements, I recommend introducing light stress tests at both the service and platform levels.
These aren’t full performance tests, but they act as early indicators of performance regressions.</p>
<p>If these three layers pass, you should have high confidence in the change.
But not complete confidence.</p>
<h2>🌙 Nightly Testing</h2>
<p>Some failures take time to appear.</p>
<p>Memory leaks, performance degradation, and cross-platform integration issues may not show up immediately.</p>
<p>That’s why I like to run a nightly build (or every few hours).</p>
<p>This environment runs end-to-end customer journey tests, performance tests, and chaos tests.</p>
<p>These are typically the same tests used during release validation, but running them continuously accelerates feedback.
If something breaks, you learn about it early, before the pressure of a release.</p>
<h2>🧠 Final Thoughts</h2>
<p>There is no universal approach everyone can follow.</p>
<p>Different systems have different needs; mission-critical systems may focus heavily on correctness and resilience.
Non-mission-critical systems may focus more on validating core functionality.</p>
<p>Your testing strategy depends heavily on architecture, dependencies, and operational constraints.
But if your organization is increasing its ability to generate code quickly, your testing capabilities must evolve at the same pace.</p>
<p>AI-generated code becomes much easier to review when you already have high confidence in your testing.</p>
]]></description>
        <pubDate>Thu, 26 Mar 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-03-26-generating-code-faster-is-only.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>When You Go to Production with gRPC, Make Sure You’ve Solved Load Distribution First</title>
        <link>https://bencane.com/posts/2026-03-19-grpc-load-distribution/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-03-19/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>When you go to production with gRPC, make sure you’ve solved load distribution first.</p>
<p>I was recently talking with another engineer who is rolling out gRPC into production.
He asked what the biggest gotchas were.</p>
<p>My first answer: Load Distribution.</p>
<h2>🚦 HTTP/1 vs. HTTP/2</h2>
<p>Most teams first implement services using REST over HTTP/1 and then migrate to gRPC as they seek its performance benefits.</p>
<p>That shift introduces a subtle but important change in how traffic gets distributed across instances.</p>
<p>With HTTP/1, requests are generally tied closely to connections. A client opens a connection, sends a request, waits for the response, and then sends another (if connection re-use is enabled).</p>
<p>HTTP/2 (which underpins gRPC) works differently.</p>
<p>HTTP/2 multiplexes requests over persistent connections.
A client can send many requests over the same connection without waiting for responses.</p>
<p>This is one of the reasons gRPC provides a performance boost, but it can create unexpected load distribution issues.</p>
<p>If your infrastructure isn’t built for an HTTP/2 world, you’ll quickly find traffic becoming unevenly distributed.</p>
<h2>🏗️ Infrastructure Support</h2>
<p>In an HTTP/1 world, load balancing at the connection (Layer 4) level often works well enough.
But with HTTP/2, connections live much longer and carry far more concurrent traffic.</p>
<p>If your load balancer distributes traffic based only on connections, a busy client may hammer a single instance while others sit idle.</p>
<p>Unfortunately, much of the infrastructure still doesn’t fully support HTTP/2-aware load balancing.</p>
<p>Depending on your environment, your load balancers or ingress controllers may operate primarily at Layer 4.
That works fine for HTTP/1, but once you introduce HTTP/2 via gRPC, the effectiveness changes significantly.</p>
<h2>⚙️ Supporting gRPC</h2>
<p>To get the most out of gRPC, the best approach is to use infrastructure that understands HTTP/2 and load-balances requests rather than just connections.</p>
<p>If that’s not possible, another option is client-side load balancing.</p>
<p>Many gRPC clients support opening a pool of connections and distributing requests across them.
You still benefit from HTTP/2’s persistent connections, but you avoid concentrating all traffic on a single backend instance.</p>
<h2>🧠 Final Thoughts</h2>
<p>gRPC offers many advantages, including performance, strongly typed contracts, and efficient communication.
But it also introduces different networking behavior.</p>
<p>If you’re rolling out gRPC into production, make sure your load balancing infrastructure is ready for an HTTP/2 world.</p>
]]></description>
        <pubDate>Thu, 19 Mar 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-03-19-when-you-go-to-production.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>You may be building for availability, but are you building for resiliency?</title>
        <link>https://bencane.com/posts/2026-03-12-availability-vs-resiliency/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-03-12/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>You may be building for availability, but are you building for resiliency?
Many teams design for availability.
Far fewer design for resiliency.</p>
<p>A concept that took me a while to really grasp is that building highly available systems and highly resilient systems is not the same thing.</p>
<p>The difference is how the system reacts to failure.</p>
<h2>🚄 High Availability</h2>
<p>When you build for high availability, the goal is simple: ensure there is always another path.</p>
<p>If something fails, traffic can be redirected somewhere else.</p>
<p>For example, a service might run across multiple availability zones or regions.
If one fails, traffic is routed to another.</p>
<p>Detecting failures and redirecting traffic are core elements of building for high availability.</p>
<p>Availability is about rerouting traffic when something fails.</p>
<h2>🚂 High Resiliency</h2>
<p>Building for resiliency is different.</p>
<p>The solution to failure isn’t another path; it’s how the system handles the error.</p>
<p>When a dependency fails, the decision becomes:</p>
<p>Do we retry?
Do we continue without that dependency?
Do we degrade functionality?
Do we stop processing altogether?</p>
<p>Resiliency is about defining what happens when things go wrong.</p>
<p>Sometimes you can continue processing.
Sometimes you can defer work and fix it later.</p>
<p>Resiliency is absorbing failure instead of avoiding it.</p>
<h2>🧩 A Simple Example</h2>
<p>When you design systems with resiliency in mind, you tend to treat dependencies differently.</p>
<p>A simple example is configuration.</p>
<p>Many systems use distributed configuration services so that runtime behavior can change without redeployment.</p>
<p>But that configuration service then becomes a dependency.
To avoid turning it into a hard dependency, many systems cache the configuration in memory.</p>
<p>When updates occur, the system fetches the new configuration and switches only after it’s fully loaded into memory.</p>
<p>If configuration refresh fails, the system continues operating with the last known configuration.
Transient failures don’t bring the system down.</p>
<p>That’s resiliency.</p>
<h2>🧠 Final Thoughts</h2>
<p>When I talk about non-functional requirements, you’ll hear me say:</p>
<p>“Highly available and resilient systems”</p>
<p>I separate them intentionally because the approaches are different.</p>
<p>Availability ensures there is always another path.
Resiliency ensures the system can continue operating when failures occur.</p>
<p>Availability routes around failure.
Resiliency survives failure.
You need both.</p>
]]></description>
        <pubDate>Thu, 12 Mar 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-03-12-you-may-be-building-for.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>When your coding agent doesn’t understand your project, you’ll get junk</title>
        <link>https://bencane.com/posts/2026-03-05-coding-agent-understands-your-project/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-03-05/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>When your coding agent doesn’t understand your project, you’ll get junk.</p>
<p>Junk in, junk out.</p>
<p>One of the best ways to get more from agentic coding tools is to give the agent context.</p>
<p>The more an agent understands your project, the better its work will be.</p>
<p>If you ask an agent to add a method to a class, it will.
It might read the file.
It might infer some structure.
But it won’t understand the project's intent.</p>
<p>If you asked a human engineer to make the same change, they would have questions.</p>
<p>What is the purpose of this project?
How is it used?
What constraints exist?</p>
<p>If they skipped that step, you’d get exactly what you asked for, even if it was wrong.</p>
<p>That’s the same challenge many face with coding agents.
A lack of context means it only does what it’s told — which isn’t always what you actually need.</p>
<p>But when it understands a project, it operates with far more clarity.</p>
<h2>🧙‍♂️ My “Old School” Method</h2>
<p>Before I start serious work with an agent, I have it learn the project.</p>
<p>Read the docs 📚
Review the codebase ⚙️
Understand the architecture 🏙️
Learn how to build, test, and run the project locally 👩‍🔧</p>
<p>I even ask the agent to summarize its understanding back to me.</p>
<p>This started as a saved prompt, turned into a slash command, and is now a skill.</p>
<p>This step is a huge productivity boost.</p>
<h2>🤖 Agents Files (<code>AGENTS.md</code>)</h2>
<p>Over the past year, an open standard for providing agents with structured context has emerged.</p>
<p>Instead of prompting the agent to rediscover your project every time, document that context once — and the agent will reference it going forward.</p>
<p>Most modern agents support an Agents.md file and reference it during each interaction.</p>
<h2>💽 What Goes in an Agents File?</h2>
<p>Think of the Agents file as onboarding documentation, but for an agent.</p>
<p>Project context:</p>
<ul>
<li>Purpose</li>
<li>Architecture</li>
<li>Layout</li>
<li>CI/CD instructions</li>
</ul>
<p>Team context:</p>
<ul>
<li>Code style preferences</li>
<li>Testing philosophy (TDD or YOLO)</li>
<li>Tech stack constraints</li>
</ul>
<p>Any tribal knowledge you’d expect a new team member to learn belongs in an Agents file.</p>
<h2>👨‍💻 Personal Agent Files</h2>
<p>Many tools also support a personal Agents file in your home directory.</p>
<p>That’s where your workflow preferences live. Are you a two-space tabs person? Do you want your agent to prefer table tests?</p>
<p>If you have preferences you want to apply to every project, but are unique to you, they go in the personal Agents file.</p>
<h2>🧠 Final Thoughts</h2>
<p>Using an Agents file dramatically improves agent quality.</p>
<p>Even then, I still use my “learn-this” slash command — sometimes that extra context makes a difference.</p>
<p>If you wouldn’t drop a new engineer into a project without context, don’t do it to your agents.</p>
]]></description>
        <pubDate>Thu, 05 Mar 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-03-05-when-your-coding-agent-doesnt.png" medium="image" />
        
      </item>
    
      
      
      
      
        
      
      <item>
        <title>You can have 100% Code Coverage and still have ticking time bombs in your code. 💣</title>
        <link>https://bencane.com/posts/2026-02-26-code-coverage-ticking-time-bombs/</link>
        
        <guid isPermaLink="false">https://bencane.com/posts/2026-02-26/</guid>
        <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Benjamin Cane</dc:creator>
        <description><![CDATA[<p>You can have 100% Code Coverage and still have ticking time bombs in your code. 💣</p>
<p>I was listening to a team recently, and an engineer was discussing how a coding agent added additional tests to a project that already had 100% code coverage.</p>
<p>The conversation reminded me that coverage is directional and often mistaken for quality.
Just because your coverage shows 100% doesn’t mean your software is fully tested.</p>
<h2>👨‍🏫 Understanding How Coverage Is Measured</h2>
<p>Code Coverage measures the percentage of executable lines that run during code tests.
Executed doesn’t mean well-tested.</p>
<p>Just because every function runs doesn’t mean it’s free of logic errors or safe.</p>
<h2>😃 Happy Path Testing</h2>
<p>A common challenge teams face with testing is focusing too much on the happy path.</p>
<p>Suppose you have a function that accepts an array.
In your tests, you always pass 5 elements — because that’s the expected usage.
Coverage shows all branches executed. You’re good, right?</p>
<p>What happens if you pass 4 elements? Or 0?</p>
<p>If you never test fewer than 5, how do you know?
You may say: “But wait, it’s only ever called with 5 elements.”
That may be true, for now.</p>
<h2>⚠️ Protecting Against Your Future Self</h2>
<p>Code is rarely static; someone will come along and change things.
That might be you, it might be someone else.</p>
<p>Eventually someone changes that function.
Will they add tests for new edge cases? Maybe.
Assume they won’t.</p>
<p>When you write tests, don’t just focus on how you know a function is going to be used; also include tests that misuse the function.</p>
<p>Rather than sending an array with 5 elements, send one with 4, 0, and send a nil value.</p>
<p>Rather than sending strings that match an expected pattern, send junk that doesn’t.</p>
<p>Does the function still behave correctly? Should it?</p>
<p>The more you test outside the happy path, the more resilient your code becomes — and the less likely it is to break later.</p>
<h2>🧠 Final Thoughts</h2>
<p>Code coverage is a guide, don’t let it give you false confidence.
Test the happy path, and the unexpected ones.
Validate function outputs against the input you provide.</p>
<p>100% Coverage is easy.
Writing reliable code is not.</p>
]]></description>
        <pubDate>Thu, 26 Feb 2026 24:00:00 GMT</pubDate>
        
          <media:content url="https://bencane.com/assets/images/posts/2026-02-26-you-can-have-100-code.png" medium="image" />
        
      </item>
    
  </channel>
</rss>
