<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Microservices | Law Zava</title><link>https://lawzava.com/topics/microservices/</link><description>Service boundaries, extraction timing, and when a modular monolith is the stronger answer.</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Thu, 13 Aug 2026 07:51:57 +0000</lastBuildDate><atom:link href="https://lawzava.com/topics/microservices/index.xml" rel="self" type="application/rss+xml"/><item><title>Testing Microservices Without Losing Your Mind</title><link>https://lawzava.com/blog/2022-09-19-testing-microservices/</link><pubDate>Mon, 19 Sep 2022 00:00:00 +0000</pubDate><guid>https://lawzava.com/blog/2022-09-19-testing-microservices/</guid><description>Microservices fail at the seams. A layered test strategy that keeps feedback fast and catches integration issues before production.</description><content:encoded><![CDATA[<h2 id="quick-take">Quick take</h2>
<p>More end-to-end tests isn&rsquo;t the answer. Push coverage down: unit and component tests for speed, contract tests for boundaries, and a thin layer of integration tests for the flows that actually matter. If your test suite takes 30 minutes, it isn&rsquo;t a test suite &ndash; it&rsquo;s a deployment blocker.</p>
<p>Microservices promise independent deployability. What they deliver is independent failure modes that span network boundaries, team boundaries, and time zones. A contract change in Service A breaks a downstream flow in Service B, and nobody finds out until staging &ndash; or worse, production.</p>
<p>The instinct is to add more end-to-end tests. That instinct is wrong. E2E tests are slow, brittle, and expensive to maintain. The teams I&rsquo;ve seen ship confidently do less at the top of the testing stack and more at the bottom.</p>
<h2 id="where-microservices-actually-break">Where Microservices Actually Break</h2>
<p>They break at the seams. Not inside the service &ndash; inside the service, a monolith and a microservice look the same. They break at the HTTP boundary, the message schema, the shared database assumption, the &ldquo;this field is always present&rdquo; contract that nobody wrote down.</p>
<p>At a large consumer platform, we had a service that returned a JSON field as a string. A downstream consumer parsed it as an integer. Worked fine for months because the values happened to be numeric. Then someone added a UUID-based identifier. The downstream service started throwing parse errors in production. No unit test caught it. No integration test covered that path. A contract test would have caught it on the first PR.</p>
<h2 id="the-testing-stack">The Testing Stack</h2>
<p>Think of it as a funnel. Wide at the bottom, narrow at the top.</p>
<h3 id="unit-tests">Unit Tests</h3>
<p>Cover business logic, edge cases, error handling. These run in milliseconds, on every commit, with no external dependencies. If your unit tests need a database connection, they aren&rsquo;t unit tests.</p>
<p>This is where the bulk of your coverage should live. 70-80% of your test count. At Decloud, we had a rule: if a bug reaches production, the fix must include a unit test that reproduces it. That rule alone improved our coverage more than any top-down testing initiative.</p>
<h3 id="component-tests">Component Tests</h3>
<p>Exercise a single service with its internals wired together. Stub or fake external dependencies. Use testcontainers for databases and message brokers when you need realistic behavior without a full environment.</p>
<p>This is where you validate that your HTTP handler correctly parses the request, calls the right service method, and returns the right response code. The external dependencies are controlled, so the test is deterministic and fast.</p>
<h3 id="contract-tests">Contract Tests</h3>
<p>This is the layer most teams skip, and it&rsquo;s the layer that prevents the most microservice-specific bugs.</p>
<p>A contract test verifies that a provider still meets the expectations of its consumers. The consumer defines what it expects &ndash; which endpoints, which fields, which status codes. The provider runs those expectations in its own CI pipeline. If a change breaks a consumer expectation, the build fails.</p>
<p>Pact is the standard tool here. It isn&rsquo;t perfect, but it closes the gap between &ldquo;Service A changed its response format&rdquo; and &ldquo;Service B found out about it three days later in staging.&rdquo;</p>
<p>If you adopt one testing practice from this post, make it contract tests.</p>
<h3 id="integration-tests">Integration Tests</h3>
<p>Reserved for cross-service flows that are too risky to simulate. Order placement, payment processing, the critical paths where real money or real user data is involved.</p>
<p>Keep the list short. Five to ten flows, covering the business-critical happy paths and the most dangerous failure modes. If the list grows to fifty, you have an environment management problem disguised as a testing strategy.</p>
<h3 id="end-to-end-tests">End-to-End Tests</h3>
<p>A handful of user journeys that represent revenue, compliance, or safety risk. Run them on a schedule or behind a merge gate, not on every commit.</p>
<p>E2E tests are where I&rsquo;ve seen the most waste. Teams build sprawling browser-driven suites that take 45 minutes to run, fail intermittently due to timing issues, and nobody trusts the results. Kill the flaky ones. Keep the critical ones. Accept that E2E is a smoke test, not a safety net.</p>
<h2 id="test-data">Test Data</h2>
<p>The quiet killer. Shared test databases with stale fixtures. Service A&rsquo;s tests depend on data that Service B&rsquo;s tests modified. Nondeterministic failures that nobody can reproduce locally.</p>
<p>Generate test data programmatically. Keep fixtures minimal. If you need production-like data, scrub it, subset it, and version it. Determinism matters more than realism.</p>
<p>At a large consumer platform, we moved from shared test databases to per-test database instances using testcontainers. Test isolation improved. Flaky test rate dropped by half. The extra CI time was worth it.</p>
<h2 id="ci-that-matches-the-layers">CI That Matches the Layers</h2>
<ul>
<li>Unit and component tests: every commit, every PR. Must be fast (under 5 minutes for the full suite).</li>
<li>Contract tests: every PR, run in both consumer and provider pipelines.</li>
<li>Integration tests: on merge to main or behind a manual gate.</li>
<li>E2E tests: scheduled runs or pre-release gates.</li>
</ul>
<p>If your fastest test layer takes more than 5 minutes, fix it before adding more tests. Developer feedback loops matter more than coverage percentages.</p>
<h2 id="the-shape-that-works">The Shape That Works</h2>
<p>Push coverage down. Most of your tests should be fast, deterministic, and runnable on a laptop. Contract tests protect the seams where microservices actually break. Integration and E2E tests cover the thin slice of flows where simulation isn&rsquo;t enough.</p>
<p>The teams that ship confidently aren&rsquo;t the ones with the most tests. They&rsquo;re the ones with the right tests in the right layers, running fast enough that developers actually wait for them.</p>
]]></content:encoded></item><item><title>Distributed Systems Patterns I Keep Reaching For</title><link>https://lawzava.com/blog/2022-05-30-distributed-systems-patterns/</link><pubDate>Mon, 30 May 2022 00:00:00 +0000</pubDate><guid>https://lawzava.com/blog/2022-05-30-distributed-systems-patterns/</guid><description>The patterns that actually survive production across failure handling, consistency, messaging, coordination, and scaling.</description><content:encoded><![CDATA[<h2 id="quick-take">Quick take</h2>
<p>Most distributed systems advice reads like a textbook. This is the shortlist I actually use. Timeouts, retries, circuit breakers, sagas, outbox/inbox, and backpressure &ndash; applied with discipline, not ceremony.</p>
<p>I&rsquo;ve built and operated distributed systems at Verizon, AT&amp;T, Decloud, and most recently at a large consumer platform. The failure modes are remarkably consistent across all of them. Network partitions, cascading timeouts, retry storms, stale caches. The systems that survive aren&rsquo;t the clever ones. They&rsquo;re the ones with boring, well-applied patterns.</p>
<p>This isn&rsquo;t a catalog. It&rsquo;s the set of patterns I keep reaching for, along with real tradeoffs and code where it helps.</p>
<h2 id="failure-handling">Failure Handling</h2>
<h3 id="timeouts-and-deadlines">Timeouts and Deadlines</h3>
<p>Every remote call without a timeout is a bug waiting to happen. I learned this the hard way at Decloud &ndash; a single downstream service that started responding in 30 seconds instead of 300 milliseconds brought down our entire checkout flow. No timeout, no deadline propagation, no circuit breaker. Just threads piling up until the JVM ran out of memory.</p>
<p>Propagate deadlines end to end. If the user&rsquo;s request has 2 seconds left, the downstream call should know that. In Go, this is <code>context.WithTimeout</code> and it works beautifully:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">fetchUser</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">id</span> <span style="color:#66d9ef">string</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#ae81ff">500</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">NewRequestWithContext</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#e6db74">&#34;GET&#34;</span>, <span style="color:#a6e22e">userServiceURL</span><span style="color:#f92672">+</span><span style="color:#e6db74">&#34;/&#34;</span><span style="color:#f92672">+</span><span style="color:#a6e22e">id</span>, <span style="color:#66d9ef">nil</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">httpClient</span>.<span style="color:#a6e22e">Do</span>(<span style="color:#a6e22e">req</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;fetch user %s: %w&#34;</span>, <span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">Body</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">u</span> <span style="color:#a6e22e">User</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">NewDecoder</span>(<span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">Body</span>).<span style="color:#a6e22e">Decode</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">u</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The caller sets the budget. The callee respects it. Simple.</p>
<h3 id="retry-with-backoff-and-jitter">Retry With Backoff and Jitter</h3>
<p>Retries fix transient errors. Immediate retries create storms. I&rsquo;ve seen a single retry loop without jitter generate enough traffic to keep a recovering service down for an extra twenty minutes.</p>
<p>Exponential backoff with full jitter. Cap the attempts. Only retry on errors that are actually transient &ndash; a 400 isn&rsquo;t transient, a 503 probably is.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">retryWithBackoff</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">maxAttempts</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">fn</span> <span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">error</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">attempt</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">attempt</span> &lt; <span style="color:#a6e22e">maxAttempts</span>; <span style="color:#a6e22e">attempt</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">fn</span>(); <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">attempt</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">maxAttempts</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">backoff</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>(<span style="color:#ae81ff">1</span><span style="color:#f92672">&lt;&lt;</span>uint(<span style="color:#a6e22e">attempt</span>)) <span style="color:#f92672">*</span> <span style="color:#ae81ff">100</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">jitter</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>(<span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Int63n</span>(int64(<span style="color:#a6e22e">backoff</span>)))
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">select</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>():
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Err</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">After</span>(<span style="color:#a6e22e">jitter</span>):
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;after %d attempts: %w&#34;</span>, <span style="color:#a6e22e">maxAttempts</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="circuit-breaker">Circuit Breaker</h3>
<p>After a threshold of errors, stop calling the failing dependency. Just stop. Return a degraded response, a cached value, or an honest error. Let the dependency recover without your traffic making things worse.</p>
<p>The mental model: closed (normal), open (failing, short-circuit), half-open (testing recovery). The implementation doesn&rsquo;t need to be complex. A counter, a timestamp, and a threshold.</p>
<h3 id="bulkheads">Bulkheads</h3>
<p>At a large consumer platform, we had a service that talked to six downstream dependencies through the same HTTP client pool. One slow dependency drained the pool and every other call started timing out too. Classic.</p>
<p>Separate connection pools per dependency. Separate goroutine budgets. The Titanic metaphor is overused but accurate &ndash; bulkheads keep one leak from sinking the whole ship.</p>
<h3 id="rate-limiting">Rate Limiting</h3>
<p>A predictable 429 is better than an unpredictable timeout. Always. Apply rate limits at the edge and between services. I&rsquo;ll talk more about this in a future post.</p>
<h2 id="consistency-and-transactions">Consistency and Transactions</h2>
<h3 id="sagas">Sagas</h3>
<p>Distributed transactions across service boundaries don&rsquo;t work. Full stop. Two-phase commit sounds great in a database textbook and falls apart the moment you have services owned by different teams with different SLAs.</p>
<p>Sagas replace a global transaction with local transactions and compensating actions. Two flavors:</p>
<p><strong>Choreography</strong>: services react to events. Decentralized, but the flow gets hard to trace once you have more than four or five steps. Debugging a choreographed saga during an incident is an exercise in grep and prayer.</p>
<p><strong>Orchestration</strong>: a coordinator drives the flow. Easier to understand, easier to audit, easier to debug. The coordinator becomes a critical path, so it needs to be durable. I default to orchestration unless the flow is truly simple.</p>
<h3 id="outbox-and-inbox">Outbox and Inbox</h3>
<p>You can&rsquo;t publish an event and update a database atomically with two separate calls. The outbox pattern solves this: write the event to a table in the same transaction as your state change. A separate publisher reads the outbox and sends the events.</p>
<p>The flip side is duplicate delivery. The inbox pattern handles that &ndash; record processed event IDs at the consumer and skip repeats. Pair them together. This is the backbone of every reliable event pipeline I&rsquo;ve built.</p>
<h3 id="idempotency">Idempotency</h3>
<p>Make writes idempotent. Use client-provided idempotency keys. Store the result keyed by that token. If the same request shows up twice, return the same result without doing the work again.</p>
<p>This isn&rsquo;t optional in a system with retries. If you have retries (and you should), you need idempotency.</p>
<h3 id="cqrs-and-read-models">CQRS and Read Models</h3>
<p>Strong consistency across services is expensive and usually unnecessary. Separate your write model from your read model. Accept eventual consistency for queries, search, and reporting. The read model can be denormalized, optimized, and rebuilt without touching the write path.</p>
<h2 id="messaging">Messaging</h2>
<h3 id="events">Events</h3>
<p>Events decouple services in time and deployment. Two styles worth knowing:</p>
<ul>
<li><strong>Event notification</strong>: &ldquo;something happened, look it up if you care.&rdquo; Lightweight, but consumers need access to the source.</li>
<li><strong>Event-carried state</strong>: &ldquo;something happened, here are the details.&rdquo; Heavier payload, but consumers are self-sufficient.</li>
</ul>
<p>Keep schemas versioned and backward compatible. Breaking an event schema in production is one of those things you only do once.</p>
<h3 id="work-queues">Work Queues</h3>
<p>Queues buffer work and let you process at a steady rate. Multiple consumers on the same queue give you horizontal scaling for free. Set visibility timeouts, handle retries explicitly, and always have a dead-letter queue for messages that can&rsquo;t be processed.</p>
<h2 id="coordination">Coordination</h2>
<h3 id="leader-election">Leader Election</h3>
<p>Some work must be done by exactly one node. Scheduling, cleanup, deduplication. Use leader election with lease-based expiration. If the leader dies, the lease expires, and someone else takes over.</p>
<p>I&rsquo;ve seen teams try to avoid leader election by distributing coordination across all nodes. It always ends with split-brain bugs that take weeks to reproduce.</p>
<h3 id="distributed-locks">Distributed Locks</h3>
<p>Use sparingly. A distributed lock should protect a small, short-lived critical section. If you find yourself holding a lock for seconds, you probably need a different design &ndash; partition by key or use a single-writer pattern.</p>
<h2 id="scaling">Scaling</h2>
<h3 id="sharding-and-consistent-hashing">Sharding and Consistent Hashing</h3>
<p>Partition by key. When you add or remove nodes, consistent hashing minimizes the data that moves. Keep shard ownership explicit and routing predictable. Implicit sharding is debugging hell.</p>
<h3 id="backpressure">Backpressure</h3>
<p>When overloaded, shed work instead of collapsing. Bounded queues, rate limits, and explicit flow control. The system that says &ldquo;no&rdquo; gracefully is more reliable than the system that says &ldquo;yes&rdquo; and falls over.</p>
<p>This is a cultural thing too. Engineers need to be comfortable returning errors under load instead of trying to serve every request.</p>
<h2 id="observability">Observability</h2>
<p>Correlation IDs through every log and event. Latency percentiles (not averages). Error rates per dependency. Queue depth trends. Alert on symptoms &ndash; elevated latency, increasing error rates &ndash; not on individual errors.</p>
<p>Without observability, you&rsquo;re guessing. During an incident, guessing is expensive.</p>
<h2 id="the-default-kit">The Default Kit</h2>
<p>If I&rsquo;m starting a new service, this is what goes in on day one:</p>
<ul>
<li>Timeouts and deadline propagation on every remote call</li>
<li>Retries with exponential backoff and jitter for transient errors</li>
<li>Circuit breakers and bulkheads around dependencies</li>
<li>Idempotency keys for writes, inbox deduplication for events</li>
<li>Outbox pattern for reliable event publishing</li>
<li>Rate limiting and backpressure at boundaries</li>
<li>Correlation IDs and service-level metrics</li>
</ul>
<p>None of this is novel. That&rsquo;s the point. Distributed systems are messy by nature. Patterns make the mess predictable. Discipline over heroics.</p>
]]></content:encoded></item><item><title>GraphQL Federation: I'm Still Skeptical</title><link>https://lawzava.com/blog/2021-10-04-graphql-federation/</link><pubDate>Mon, 04 Oct 2021 00:00:00 +0000</pubDate><guid>https://lawzava.com/blog/2021-10-04-graphql-federation/</guid><description>A year after my GraphQL post, federation is the new hotness. I still think most teams don&amp;amp;rsquo;t need it.</description><content:encoded><![CDATA[<p>I wrote about GraphQL  <a href="/blog/2020-08-17-graphql-federation/"
   
   >last year</a>
 and my position was: it solves real problems but adds real complexity, and most teams underestimate the operational cost. A year later, federation is the hot topic. Apollo is pushing it hard. Conference talks everywhere. Teams that just adopted GraphQL are now being told they need to federate.</p>
<p>I remain skeptical.</p>
<h2 id="the-problem-federation-solves">The problem federation solves</h2>
<p>Federation exists because a single GraphQL server becomes a coordination bottleneck when multiple teams contribute to it. Schema changes need cross-team approval. A single deploy touches unrelated domains. One bad resolver takes down the whole API.</p>
<p>Federation fixes this by splitting the graph into subgraphs owned by different teams, composed behind a gateway. Each team deploys independently. The gateway stitches it together.</p>
<p>This is a real problem. At scale. With many teams. With a large, actively-evolving API surface.</p>
<h2 id="the-problem-is-most-teams-arent-there">The problem is: most teams aren&rsquo;t there</h2>
<p>Every team I&rsquo;ve talked to this year that&rsquo;s considering federation has fewer than 30 engineers. Most have fewer than 15. They don&rsquo;t have a coordination bottleneck because they don&rsquo;t have enough teams to create one.</p>
<p>What they have is a single GraphQL server that works. And someone read an Apollo blog post and now there&rsquo;s a ticket to &ldquo;federate the graph.&rdquo;</p>
<p>Federation introduces a gateway with a query planner. It introduces composition &ndash; a build step where subgraph schemas are merged, and breaking changes are detected. It introduces entity resolution across subgraphs, which means you need to think about N+1 fetches at the gateway layer. It introduces distributed tracing across subgraph boundaries. It introduces versioning and compatibility rules for shared types.</p>
<p>All of that&rsquo;s manageable. None of it&rsquo;s free.</p>
<h2 id="when-it-actually-makes-sense">When it actually makes sense</h2>
<p>You need federation when:</p>
<ul>
<li>Multiple teams (3+) independently contribute to the API</li>
<li>Schema changes in one domain regularly block another team</li>
<li>You need independent deployment of API segments</li>
<li>The API surface is large enough that one team can&rsquo;t own it</li>
</ul>
<p>If those are true, federate. The organizational benefits are worth the operational cost.</p>
<p>If you&rsquo;re a single team, or two teams that talk to each other daily, a monolithic GraphQL server with good module boundaries is simpler, faster to debug, and cheaper to operate. You don&rsquo;t need a query planner. You need a code review.</p>
<h2 id="the-n1-problem-gets-worse">The N+1 problem gets worse</h2>
<p>GraphQL already has an N+1 problem that DataLoader solves at the resolver level. Federation moves the N+1 problem up to the gateway. The query planner decides how to fetch entities across subgraphs, and it&rsquo;s easy to end up with serial round-trips that destroy your latency.</p>
<p>You can mitigate this with batched entity resolution and careful schema design. But it&rsquo;s another thing to get right, monitor, and debug. In a monolithic GraphQL server, a slow resolver is one function call away. In a federated graph, it&rsquo;s a network hop through a query planner into a subgraph you might not own.</p>
<h2 id="my-recommendation">My recommendation</h2>
<p>If you&rsquo;re using GraphQL today and it&rsquo;s working, keep it. Don&rsquo;t federate because a blog post told you to.</p>
<p>If you&rsquo;re genuinely hitting coordination problems &ndash; teams blocking each other on schema changes, deploys causing cross-domain risk, ownership unclear &ndash; then federation is a reasonable solution. Invest in the composition pipeline, the observability, and the entity resolution patterns. Treat it as a significant infrastructure investment.</p>
<p>And if you&rsquo;re still deciding whether to adopt GraphQL at all, I&rsquo;ll repeat what I said last year: REST with good API design and versioning covers most use cases. GraphQL is worth it when you have diverse clients with genuinely different data needs. Federation is worth it on top of that only when you have the team scale to justify the complexity.</p>
<p>Most teams should stop adding layers and start shipping features.</p>
]]></content:encoded></item><item><title>API Gateway Patterns That Actually Work</title><link>https://lawzava.com/blog/2021-05-31-api-gateway-patterns/</link><pubDate>Mon, 31 May 2021 00:00:00 +0000</pubDate><guid>https://lawzava.com/blog/2021-05-31-api-gateway-patterns/</guid><description>Edge gateways, BFFs, and service mesh ingress &amp;amp;ndash; what I&amp;amp;rsquo;ve learned running them at Decloud and at large telecoms.</description><content:encoded><![CDATA[<h2 id="quick-take">Quick take</h2>
<p>Your gateway should be boring. Pick one pattern that matches your clients, keep it thin (auth, rate limits, routing), and treat config changes like code deploys. Most gateway disasters come from stuffing business logic where it doesn&rsquo;t belong.</p>
<hr>
<p>I&rsquo;ve built or inherited API gateways at every company I&rsquo;ve worked with. At Decloud we ran a single edge gateway in front of about a dozen services. At a large telecom, I walked into a gateway that had become a second application server &ndash; hundreds of Kong plugins, custom Lua transformations, business rules nobody could trace back to a ticket. It took three engineers two months to untangle.</p>
<p>The gateway itself was fine. The pattern choice was the problem.</p>
<h2 id="when-you-actually-need-one">When you actually need one</h2>
<p>Not every system needs an API gateway. If you have one service and one client, a gateway is overhead. You need a gateway when you have multiple services exposed externally, or when you have distinct client types (web, mobile, partner APIs) that need different response shapes.</p>
<p>At Decloud we hit that threshold around service number four. Before that, we just had nginx doing TLS termination and basic routing. That was fine. The moment we needed per-client rate limits and centralized auth token validation, we added a proper gateway. Not before.</p>
<h2 id="the-patterns-worth-knowing">The patterns worth knowing</h2>
<h3 id="edge-gateway">Edge gateway</h3>
<p>Single entry point. All external traffic flows through it. This is where most teams should start and where many teams should stop.</p>
<p>You get centralized auth, rate limiting, and routing. Your services stay behind a private network. The config is one place, the logs are one place, the failure mode is one place. I like boring. Boring is debuggable.</p>
<p>At Decloud, our edge gateway handled TLS termination, JWT validation, rate limits, and request ID injection. That&rsquo;s it. We resisted every request to add &ldquo;just one more transformation.&rdquo; Every single time someone wanted to add response shaping or field filtering at the gateway layer, the answer was no. Put it in a service.</p>
<h3 id="backend-for-frontend-bff">Backend for Frontend (BFF)</h3>
<p>A separate gateway per client type. Your mobile app gets one gateway that returns compact payloads. Your web app gets another. Your partner API gets a third.</p>
<p>I ended up recommending this at a telecom where the mobile team and web team were in a constant fight over the shape of API responses. The mobile team needed tiny payloads. The web team wanted rich nested objects. One gateway couldn&rsquo;t make both happy without turning into a mess of conditional logic.</p>
<p>BFFs solve this cleanly. Each team owns their gateway, shapes responses for their client, and deploys independently. The tradeoff is more gateways to operate. Worth it when the alternative is a god-gateway that tries to please everyone.</p>
<h3 id="service-mesh-ingress">Service mesh ingress</h3>
<p>If you&rsquo;re already running Istio or Linkerd inside your cluster, your mesh&rsquo;s ingress gateway can handle the edge. You get mTLS, traffic shifting, and telemetry for free because the mesh is already doing that internally.</p>
<p>I&rsquo;d only go this route if the mesh is already in place and working. Don&rsquo;t adopt a service mesh just to get a gateway. That&rsquo;s backwards.</p>
<h2 id="what-belongs-in-the-gateway">What belongs in the gateway</h2>
<p>Keep this list short. I mean it.</p>
<ul>
<li>TLS termination</li>
<li>Auth token validation (not the auth logic itself, just &ldquo;is this token valid&rdquo;)</li>
<li>Rate limiting</li>
<li>Request ID injection</li>
<li>Consistent error formatting</li>
<li>Basic routing</li>
</ul>
<p>A typical route config should be dead simple:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">routes</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">path</span>: <span style="color:#ae81ff">/api/orders</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">upstream</span>: <span style="color:#ae81ff">orders-service</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">timeout_ms</span>: <span style="color:#ae81ff">3000</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">rate_limit_per_minute</span>: <span style="color:#ae81ff">120</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">auth</span>: <span style="color:#ae81ff">required</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">add_headers</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">X-Request-ID</span>: <span style="color:#ae81ff">$request_id</span>
</span></span></code></pre></div><p>If your gateway config requires a senior engineer to review, it&rsquo;s too complex.</p>
<h2 id="what-doesnt-belong-in-the-gateway">What doesn&rsquo;t belong in the gateway</h2>
<p>This is the part people get wrong.</p>
<ul>
<li>Business validation. (&ldquo;Is this order amount valid?&rdquo; No. That&rsquo;s a service concern.)</li>
<li>Response aggregation across multiple services. (That&rsquo;s a BFF or a dedicated orchestration service.)</li>
<li>Data transformation beyond trivial header manipulation.</li>
<li>Anything that requires state.</li>
</ul>
<p>I watched a team spend six months debugging production issues that all traced back to a Kong plugin doing field-level authorization. The plugin had its own config, its own cache, its own failure modes. It was invisible to application-level monitoring. When it failed, the errors looked like they came from downstream services.</p>
<p>Don&rsquo;t do this.</p>
<h2 id="operational-discipline">Operational discipline</h2>
<p>A gateway is in every request path. Treat it accordingly.</p>
<p><strong>Timeouts aren&rsquo;t optional.</strong> At one company, I found a gateway with no timeout configuration. A slow downstream service caused requests to pile up at the gateway until it ran out of connections. The entire platform went down because one service was having a bad day. Set timeouts. Set them tight. 3-5 seconds for most APIs. If an operation takes longer, it probably shouldn&rsquo;t be synchronous.</p>
<p><strong>Retries need limits.</strong> One retry with jitter is usually fine. Aggressive retry policies create thundering herds. I&rsquo;ve seen a gateway with 3 retries and no backoff take a struggling service from &ldquo;slow&rdquo; to &ldquo;completely dead&rdquo; in under a minute.</p>
<p><strong>Config changes are deploys.</strong> Version your gateway config. Review it. Roll it out gradually. A bad config change at the gateway layer is an outage for every service behind it.</p>
<h2 id="choosing-a-technology">Choosing a technology</h2>
<p>In 2021, I see three real options:</p>
<p><strong>Kong</strong> if you want plugin-driven API management and fast setup. Good ecosystem, reasonable operational story. Watch out for plugin sprawl.</p>
<p><strong>Envoy</strong> if you want deep traffic control and high performance. Steeper learning curve. Better fit for teams that already think in terms of proxies and traffic policy.</p>
<p><strong>AWS API Gateway</strong> if you&rsquo;re all-in on AWS and want managed infrastructure. Less flexibility, but less operational burden. Fine for most REST APIs.</p>
<p>I&rsquo;ve used all three. For most teams, Kong or Envoy behind a load balancer covers everything you need. If you&rsquo;re on AWS and don&rsquo;t want to operate infrastructure, the managed option is perfectly reasonable.</p>
<h2 id="the-real-lesson">The real lesson</h2>
<p>The best gateways I&rsquo;ve seen are the ones nobody talks about. They&rsquo;re boring. They do five things well. They have clean configs, good logs, and predictable failure modes.</p>
<p>The worst gateways are the ones that became &ldquo;the platform.&rdquo; Business logic, custom transformations, complex routing rules that only one person understands. Once a gateway reaches that point, you&rsquo;re not operating a gateway anymore. You&rsquo;re operating a distributed monolith with extra latency.</p>
<p>Keep it thin. Keep it boring. Your future self will thank you.</p>
]]></content:encoded></item><item><title>gRPC Patterns That Actually Work in Production</title><link>https://lawzava.com/blog/2020-05-11-grpc-best-practices/</link><pubDate>Mon, 11 May 2020 00:00:00 +0000</pubDate><guid>https://lawzava.com/blog/2020-05-11-grpc-best-practices/</guid><description>Hard-won gRPC patterns from building Decloud&amp;amp;rsquo;s service mesh. Proto design, Go implementation, error handling, and the mistakes that cost us weekends.</description><content:encoded><![CDATA[<h2 id="quick-take">Quick take</h2>
<p>Stop treating gRPC like REST with a different wire format. Design protos for evolution from day one, always set deadlines, use interceptors for everything cross-cutting, and test with real servers. I share the exact patterns we use at Decloud with Go code you can steal.</p>
<hr>
<p>We moved Decloud&rsquo;s internal APIs to gRPC about eight months ago. Before that, everything was REST with hand-rolled JSON serialization, version mismatches everywhere, and a weekly ritual of &ldquo;why is the client sending a string where we expect an int.&rdquo; Moving to gRPC fixed the type safety problem. It also introduced a whole new category of problems I didn&rsquo;t anticipate.</p>
<p>This is what I wish someone had written down before we started. Real patterns, real Go code, real mistakes.</p>
<h2 id="why-we-picked-grpc-and-where-we-didnt">Why we picked gRPC (and where we didn&rsquo;t)</h2>
<p>Short version: internal service-to-service calls where latency matters and both sides are services we control. That&rsquo;s it. That&rsquo;s the use case.</p>
<p>We kept REST for:</p>
<ul>
<li>Anything browser-facing. gRPC-Web exists but it&rsquo;s a subset and adds a proxy layer. Not worth it for us.</li>
<li>Third-party integrations. Nobody wants to learn your proto schema.</li>
<li>Simple CRUD admin tools. <code>curl</code> is a better debugger than <code>grpcurl</code> and I don&rsquo;t care what anyone says.</li>
</ul>
<p>The real win was code generation. We&rsquo;ve Go, Python, and a small Rust service. Generating typed clients from one <code>.proto</code> file eliminated an entire class of integration bugs. Before gRPC, every language had its own hand-written client that drifted independently. Now drift is a compilation error.</p>
<h2 id="proto-design-get-this-wrong-and-youll-pay-for-years">Proto design: get this wrong and you&rsquo;ll pay for years</h2>
<p>Field numbers are forever. I mean that literally. Once you ship a proto, those field numbers are carved into every binary that&rsquo;s ever been compiled against it. Get the schema design wrong early and you&rsquo;re living with it or doing a painful migration.</p>
<p>Here&rsquo;s what our service definitions actually look like at Decloud:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;proto3&#34;</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#f92672">package</span> decloud<span style="color:#f92672">.</span>nodes.v1;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">import</span> <span style="color:#e6db74">&#34;google/protobuf/timestamp.proto&#34;</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">service</span> NodeService {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">rpc</span> GetNode(GetNodeRequest) <span style="color:#66d9ef">returns</span> (Node);<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">rpc</span> ListNodes(ListNodesRequest) <span style="color:#66d9ef">returns</span> (ListNodesResponse);<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">rpc</span> WatchNodeStatus(WatchNodeStatusRequest) <span style="color:#66d9ef">returns</span> (stream NodeStatusEvent);<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">message</span> <span style="color:#a6e22e">GetNodeRequest</span> {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">string</span> node_id <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">message</span> <span style="color:#a6e22e">ListNodesRequest</span> {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">int32</span> page_size <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">string</span> page_token <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  NodeFilter filter <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">message</span> <span style="color:#a6e22e">ListNodesResponse</span> {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">repeated</span> Node nodes <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">string</span> next_page_token <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">message</span> <span style="color:#a6e22e">NodeFilter</span> {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">repeated</span> <span style="color:#66d9ef">string</span> regions <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  NodeStatus status <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">message</span> <span style="color:#a6e22e">Node</span> {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">string</span> id <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">string</span> hostname <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  NodeStatus status <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  google.protobuf.Timestamp created_at <span style="color:#f92672">=</span> <span style="color:#ae81ff">4</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  google.protobuf.Timestamp last_heartbeat <span style="color:#f92672">=</span> <span style="color:#ae81ff">5</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  NodeResources resources <span style="color:#f92672">=</span> <span style="color:#ae81ff">6</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  reserved <span style="color:#ae81ff">7</span>, <span style="color:#ae81ff">8</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  reserved <span style="color:#e6db74">&#34;legacy_provider&#34;</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">message</span> <span style="color:#a6e22e">NodeResources</span> {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">int64</span> cpu_millicores <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">int64</span> memory_bytes <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">int64</span> disk_bytes <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">enum</span> NodeStatus {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  NODE_STATUS_UNSPECIFIED <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  NODE_STATUS_PROVISIONING <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  NODE_STATUS_READY <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  NODE_STATUS_DRAINING <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  NODE_STATUS_OFFLINE <span style="color:#f92672">=</span> <span style="color:#ae81ff">4</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">message</span> <span style="color:#a6e22e">WatchNodeStatusRequest</span> {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">string</span> node_id <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">message</span> <span style="color:#a6e22e">NodeStatusEvent</span> {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">string</span> node_id <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  NodeStatus previous <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  NodeStatus current <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  google.protobuf.Timestamp occurred_at <span style="color:#f92672">=</span> <span style="color:#ae81ff">4</span>;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>A few things worth noting:</p>
<p><strong>Every RPC gets its own request and response messages.</strong> Even if <code>GetNodeRequest</code> only has one field today. You will add fields later. If you use a raw <code>string</code> as your request type, you&rsquo;ve locked yourself out of adding filters, field masks, or anything else without a breaking change.</p>
<p><strong>Enums start with <code>_UNSPECIFIED = 0</code>.</strong> Proto3 defaults to zero. If you put a meaningful value at zero, you can&rsquo;t distinguish &ldquo;the client explicitly set this&rdquo; from &ldquo;the client didn&rsquo;t set it.&rdquo; We learned this the hard way with a status enum that defaulted to <code>ACTIVE</code> at zero. Debugging phantom active nodes wasn&rsquo;t fun.</p>
<p><strong>Reserve removed fields.</strong> See that <code>reserved 7, 8</code> and <code>reserved &quot;legacy_provider&quot;</code>? Those are fields we removed during a redesign. Without the reservation, someone could reuse field number 7 for a completely different type and corrupt data in old clients that still have the old schema cached.</p>
<p><strong>Use <code>Timestamp</code> not <code>int64</code>.</strong> I&rsquo;ve seen people use epoch millis as int64 fields. Looks fine until you&rsquo;re debugging across time zones and nobody remembers whether it&rsquo;s seconds or milliseconds or which epoch.</p>
<h2 id="go-server-implementation">Go server implementation</h2>
<p>Here&rsquo;s a stripped-down but realistic server. This is close to what we actually run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;google.golang.org/grpc&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;google.golang.org/grpc/codes&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;google.golang.org/grpc/health&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">healthpb</span> <span style="color:#e6db74">&#34;google.golang.org/grpc/health/grpc_health_v1&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;google.golang.org/grpc/status&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">pb</span> <span style="color:#e6db74">&#34;github.com/decloud/api/nodes/v1&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">nodeServer</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">UnimplementedNodeServiceServer</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">store</span> <span style="color:#a6e22e">NodeStore</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">nodeServer</span>) <span style="color:#a6e22e">GetNode</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">req</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">GetNodeRequest</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">Node</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">GetNodeId</span>() <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">codes</span>.<span style="color:#a6e22e">InvalidArgument</span>, <span style="color:#e6db74">&#34;node_id is required&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">node</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">GetNodeId</span>())
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#a6e22e">codes</span>.<span style="color:#a6e22e">Internal</span>, <span style="color:#e6db74">&#34;store lookup failed: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">node</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#a6e22e">codes</span>.<span style="color:#a6e22e">NotFound</span>, <span style="color:#e6db74">&#34;node %q not found&#34;</span>, <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">GetNodeId</span>())
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">node</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">nodeServer</span>) <span style="color:#a6e22e">WatchNodeStatus</span>(<span style="color:#a6e22e">req</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">WatchNodeStatusRequest</span>, <span style="color:#a6e22e">stream</span> <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">NodeService_WatchNodeStatusServer</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">GetNodeId</span>() <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">codes</span>.<span style="color:#a6e22e">InvalidArgument</span>, <span style="color:#e6db74">&#34;node_id is required&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">events</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">Subscribe</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">GetNodeId</span>())
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">Unsubscribe</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">GetNodeId</span>(), <span style="color:#a6e22e">events</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">select</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">Context</span>().<span style="color:#a6e22e">Done</span>():
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">event</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">events</span>:
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">Send</span>(<span style="color:#a6e22e">event</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">lis</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">net</span>.<span style="color:#a6e22e">Listen</span>(<span style="color:#e6db74">&#34;tcp&#34;</span>, <span style="color:#e6db74">&#34;:9090&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;failed to listen: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">srv</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">NewServer</span>(
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">ChainUnaryInterceptor</span>(
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">loggingInterceptor</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">recoveryInterceptor</span>,
</span></span><span style="display:flex;"><span>		),
</span></span><span style="display:flex;"><span>	)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">RegisterNodeServiceServer</span>(<span style="color:#a6e22e">srv</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">nodeServer</span>{<span style="color:#a6e22e">store</span>: <span style="color:#a6e22e">NewNodeStore</span>()})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">hsrv</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">health</span>.<span style="color:#a6e22e">NewServer</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">healthpb</span>.<span style="color:#a6e22e">RegisterHealthServer</span>(<span style="color:#a6e22e">srv</span>, <span style="color:#a6e22e">hsrv</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">hsrv</span>.<span style="color:#a6e22e">SetServingStatus</span>(<span style="color:#e6db74">&#34;decloud.nodes.v1.NodeService&#34;</span>, <span style="color:#a6e22e">healthpb</span>.<span style="color:#a6e22e">HealthCheckResponse_SERVING</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;listening on :9090&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Serve</span>(<span style="color:#a6e22e">lis</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;failed to serve: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>A few patterns here that took us a while to settle on:</p>
<p><strong>Embed <code>UnimplementedNodeServiceServer</code>.</strong> This is the forward-compatible pattern. When you add a new RPC to the proto, the server still compiles. It returns <code>Unimplemented</code> for the new method until you add the handler. Without this, adding a method to the proto breaks every server binary.</p>
<p><strong>Register the health service.</strong> This isn&rsquo;t optional. Kubernetes liveness and readiness probes need it. Our deploy pipeline rejects services that don&rsquo;t register the gRPC health check. No exceptions.</p>
<p><strong>Chain interceptors.</strong> Logging, recovery, metrics &ndash; all cross-cutting concerns go in interceptors. Not in every handler. The <code>ChainUnaryInterceptor</code> API landed relatively recently and it&rsquo;s much cleaner than the old single-interceptor pattern where you&rsquo;d nest them manually.</p>
<h2 id="client-patterns-that-dont-break-at-3am">Client patterns that don&rsquo;t break at 3am</h2>
<p>The client side is where most people get sloppy. Here&rsquo;s what we enforce:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">newNodeClient</span>(<span style="color:#a6e22e">addr</span> <span style="color:#66d9ef">string</span>) (<span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">NodeServiceClient</span>, <span style="color:#66d9ef">func</span>(), <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">5</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">conn</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">DialContext</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">addr</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">WithTransportCredentials</span>(<span style="color:#a6e22e">loadTLSCredentials</span>()),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">WithDefaultCallOptions</span>(
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">MaxCallRecvMsgSize</span>(<span style="color:#ae81ff">4</span><span style="color:#f92672">*</span><span style="color:#ae81ff">1024</span><span style="color:#f92672">*</span><span style="color:#ae81ff">1024</span>),
</span></span><span style="display:flex;"><span>		),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">WithChainUnaryInterceptor</span>(
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">retryInterceptor</span>(<span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">100</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>),
</span></span><span style="display:flex;"><span>		),
</span></span><span style="display:flex;"><span>	)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;dial %s: %w&#34;</span>, <span style="color:#a6e22e">addr</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cleanup</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">func</span>() { <span style="color:#a6e22e">conn</span>.<span style="color:#a6e22e">Close</span>() }
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">NewNodeServiceClient</span>(<span style="color:#a6e22e">conn</span>), <span style="color:#a6e22e">cleanup</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">getNodeWithDeadline</span>(<span style="color:#a6e22e">client</span> <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">NodeServiceClient</span>, <span style="color:#a6e22e">nodeID</span> <span style="color:#66d9ef">string</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">Node</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">3</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">node</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">GetNode</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">GetNodeRequest</span>{<span style="color:#a6e22e">NodeId</span>: <span style="color:#a6e22e">nodeID</span>})
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">st</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">Convert</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">st</span>.<span style="color:#a6e22e">Code</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">codes</span>.<span style="color:#a6e22e">NotFound</span>:
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#66d9ef">nil</span> <span style="color:#75715e">// not an error, just missing</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">codes</span>.<span style="color:#a6e22e">DeadlineExceeded</span>:
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;node lookup timed out after 3s&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;node lookup failed: %s&#34;</span>, <span style="color:#a6e22e">st</span>.<span style="color:#a6e22e">Message</span>())
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">node</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Always set deadlines.</strong> Every single call. No exceptions. A call without a deadline is a goroutine leak waiting to happen. We had an incident where a downstream service hung and our caller accumulated ~40,000 goroutines because nobody set a timeout. The fix was one line. The outage was three hours.</p>
<p><strong>Handle status codes explicitly.</strong> Don&rsquo;t just check <code>err != nil</code>. A <code>NotFound</code> is fundamentally different from <code>Internal</code>. Your retry policy, your alerting, your user-facing message &ndash; all different depending on the code. <code>status.Convert(err)</code> gives you the code. Use it.</p>
<p><strong>Retry with backoff, but only on the right codes.</strong> We retry on <code>Unavailable</code> and <code>DeadlineExceeded</code>. We do <em>not</em> retry on <code>InvalidArgument</code> or <code>NotFound</code>. Retrying a bad request is just a faster way to burn your quota.</p>
<h2 id="interceptors-the-grpc-middleware-pattern">Interceptors: the gRPC middleware pattern</h2>
<p>This is where gRPC really shines versus REST. Interceptors are typed, composable, and they work identically for unary and streaming RPCs. Here&rsquo;s our logging interceptor:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">loggingInterceptor</span>(
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">req</span> <span style="color:#66d9ef">interface</span>{},
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">info</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">UnaryServerInfo</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">handler</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">UnaryHandler</span>,
</span></span><span style="display:flex;"><span>) (<span style="color:#66d9ef">interface</span>{}, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">start</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">handler</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">req</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">duration</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Since</span>(<span style="color:#a6e22e">start</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">code</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">codes</span>.<span style="color:#a6e22e">OK</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">code</span> = <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">Code</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;method=%s code=%s duration=%s&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">info</span>.<span style="color:#a6e22e">FullMethod</span>, <span style="color:#a6e22e">code</span>, <span style="color:#a6e22e">duration</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">recoveryInterceptor</span>(
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">req</span> <span style="color:#66d9ef">interface</span>{},
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">info</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">UnaryServerInfo</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">handler</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">UnaryHandler</span>,
</span></span><span style="display:flex;"><span>) (<span style="color:#a6e22e">resp</span> <span style="color:#66d9ef">interface</span>{}, <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> recover(); <span style="color:#a6e22e">r</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;panic in %s: %v&#34;</span>, <span style="color:#a6e22e">info</span>.<span style="color:#a6e22e">FullMethod</span>, <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#a6e22e">codes</span>.<span style="color:#a6e22e">Internal</span>, <span style="color:#e6db74">&#34;internal error&#34;</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">handler</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">req</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The recovery interceptor has saved us more than once. A nil pointer dereference in a handler used to crash the entire server. Now it returns <code>Internal</code> and logs the panic. The server stays up. Other RPCs keep working. We still fix the bug, but we don&rsquo;t page the on-call team at 3am for a panic in a non-critical endpoint.</p>
<h2 id="error-handling-the-part-everyone-gets-wrong">Error handling: the part everyone gets wrong</h2>
<p>I&rsquo;ve seen codebases where every gRPC error is <code>codes.Internal</code>. That&rsquo;s like every HTTP response being a 500. Useless for clients. Useless for monitoring.</p>
<p>Our rule is simple: pick the status code that tells the client what to <em>do</em>.</p>
<table>
  <thead>
      <tr>
          <th>Situation</th>
          <th>Code</th>
          <th>Client action</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bad input</td>
          <td><code>InvalidArgument</code></td>
          <td>Fix the request, don&rsquo;t retry</td>
      </tr>
      <tr>
          <td>Missing resource</td>
          <td><code>NotFound</code></td>
          <td>Don&rsquo;t retry, maybe create it</td>
      </tr>
      <tr>
          <td>Duplicate creation</td>
          <td><code>AlreadyExists</code></td>
          <td>Probably idempotent, check state</td>
      </tr>
      <tr>
          <td>Auth missing/expired</td>
          <td><code>Unauthenticated</code></td>
          <td>Re-authenticate</td>
      </tr>
      <tr>
          <td>Auth valid but insufficient</td>
          <td><code>PermissionDenied</code></td>
          <td>Don&rsquo;t retry, escalate</td>
      </tr>
      <tr>
          <td>Server overloaded</td>
          <td><code>Unavailable</code></td>
          <td>Retry with backoff</td>
      </tr>
      <tr>
          <td>Bug or unknown</td>
          <td><code>Internal</code></td>
          <td>Alert, escalate</td>
      </tr>
  </tbody>
</table>
<p>The key insight: <code>Internal</code> means &ldquo;the server has a bug.&rdquo; If you&rsquo;re returning <code>Internal</code> for a missing resource or bad input, you&rsquo;re lying to your monitoring system. Your alerts will fire for things that aren&rsquo;t server bugs. Alert fatigue follows.</p>
<h2 id="proto-evolution-the-non-obvious-rules">Proto evolution: the non-obvious rules</h2>
<p>Adding fields is safe. Removing them isn&rsquo;t. That much is obvious. The non-obvious part:</p>
<p><strong>Changing a field from <code>string</code> to <code>bytes</code> is wire-compatible but semantically different.</strong> We did this once with a field that held a UUID. Wire format was identical. But the generated Go code changed from <code>string</code> to <code>[]byte</code> and broke every caller at compile time. &ldquo;Wire compatible&rdquo; and &ldquo;API compatible&rdquo; are different things.</p>
<p><strong>Removing a field without <code>reserved</code> is a time bomb.</strong> Six months later, someone reuses that field number for a different type. Old clients that haven&rsquo;t recompiled send the old type. The new server interprets it as the new type. Data corruption that only manifests in production with old client versions. Good luck debugging that.</p>
<p><strong>Version at the package level.</strong> When you need a breaking change:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#75715e">// Old clients still work
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#f92672">package</span> decloud<span style="color:#f92672">.</span>nodes.v1;<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e">// New clients use v2
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#f92672">package</span> decloud<span style="color:#f92672">.</span>nodes.v2;<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>Run both versions side by side. Migrate clients one at a time. Kill v1 when the last client is gone. We track this with a Grafana dashboard that shows request counts per proto package version.</p>
<h2 id="testing-skip-the-mocks">Testing: skip the mocks</h2>
<p>We don&rsquo;t mock gRPC clients. We spin up a real <code>grpc.Server</code> in tests with <code>bufconn</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">setupTest</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">NodeServiceClient</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Helper</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">lis</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bufconn</span>.<span style="color:#a6e22e">Listen</span>(<span style="color:#ae81ff">1024</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">1024</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">srv</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">NewServer</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">RegisterNodeServiceServer</span>(<span style="color:#a6e22e">srv</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">nodeServer</span>{<span style="color:#a6e22e">store</span>: <span style="color:#a6e22e">NewMemoryStore</span>()})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() { <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Serve</span>(<span style="color:#a6e22e">lis</span>) }()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Cleanup</span>(<span style="color:#66d9ef">func</span>() { <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">GracefulStop</span>() })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">conn</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">DialContext</span>(
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">WithContextDialer</span>(<span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">_</span> <span style="color:#66d9ef">string</span>) (<span style="color:#a6e22e">net</span>.<span style="color:#a6e22e">Conn</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">lis</span>.<span style="color:#a6e22e">DialContext</span>(<span style="color:#a6e22e">ctx</span>)
</span></span><span style="display:flex;"><span>		}),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">WithTransportCredentials</span>(<span style="color:#a6e22e">insecure</span>.<span style="color:#a6e22e">NewCredentials</span>()),
</span></span><span style="display:flex;"><span>	)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;dial bufconn: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Cleanup</span>(<span style="color:#66d9ef">func</span>() { <span style="color:#a6e22e">conn</span>.<span style="color:#a6e22e">Close</span>() })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">NewNodeServiceClient</span>(<span style="color:#a6e22e">conn</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestGetNode</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">client</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">setupTest</span>(<span style="color:#a6e22e">t</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// Happy path</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">node</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">GetNode</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">GetNodeRequest</span>{<span style="color:#a6e22e">NodeId</span>: <span style="color:#e6db74">&#34;node-1&#34;</span>})
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;unexpected error: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">node</span>.<span style="color:#a6e22e">GetHostname</span>() <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;worker-01.decloud.dev&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;hostname = %q, want %q&#34;</span>, <span style="color:#a6e22e">node</span>.<span style="color:#a6e22e">GetHostname</span>(), <span style="color:#e6db74">&#34;worker-01.decloud.dev&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// Not found</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">GetNode</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">GetNodeRequest</span>{<span style="color:#a6e22e">NodeId</span>: <span style="color:#e6db74">&#34;nonexistent&#34;</span>})
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">Code</span>(<span style="color:#a6e22e">err</span>) <span style="color:#f92672">!=</span> <span style="color:#a6e22e">codes</span>.<span style="color:#a6e22e">NotFound</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;code = %v, want NotFound&#34;</span>, <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">Code</span>(<span style="color:#a6e22e">err</span>))
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// Validation</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">GetNode</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">GetNodeRequest</span>{})
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">Code</span>(<span style="color:#a6e22e">err</span>) <span style="color:#f92672">!=</span> <span style="color:#a6e22e">codes</span>.<span style="color:#a6e22e">InvalidArgument</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;code = %v, want InvalidArgument&#34;</span>, <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">Code</span>(<span style="color:#a6e22e">err</span>))
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>bufconn</code> gives you an in-memory listener. No ports, no flaky tests from port conflicts, no network overhead. But you still test the full gRPC stack: serialization, interceptors, status codes, deadlines. Mocking the client interface skips all of that. We found real bugs with <code>bufconn</code> that mocks would have hidden &ndash; serialization of <code>oneof</code> fields, deadline propagation through interceptors, and metadata handling.</p>
<h2 id="load-balancing-the-http2-gotcha">Load balancing: the HTTP/2 gotcha</h2>
<p>gRPC runs on HTTP/2, which multiplexes requests over a single TCP connection. This means traditional L4 load balancers don&rsquo;t work. All requests go to whichever backend received the connection. One hot server, N-1 idle servers.</p>
<p>We use client-side balancing with a service registry. The gRPC resolver API lets you plug in your own discovery:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">conn</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">Dial</span>(
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;dns:///nodes.decloud.internal:9090&#34;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">WithDefaultServiceConfig</span>(<span style="color:#e6db74">`{&#34;loadBalancingPolicy&#34;:&#34;round_robin&#34;}`</span>),
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>The <code>dns:///</code> scheme tells the resolver to use DNS. <code>round_robin</code> distributes across all A records. For us this works because we run headless Kubernetes services that return pod IPs.</p>
<p>If you&rsquo;re behind  <a href="/blog/2018-11-26-service-mesh-istio-practical-guide/"
   
   >Envoy or Istio</a>
, make sure they&rsquo;re configured for HTTP/2, not HTTP/1.1 upgrade. I&rsquo;ve seen service meshes silently downgrade gRPC to HTTP/1.1 and nobody notices until streaming breaks.</p>
<h2 id="what-id-do-differently">What I&rsquo;d do differently</h2>
<p>Eight months in, here&rsquo;s what I&rsquo;d change if we started over:</p>
<p><strong>Invest in proto linting from day one.</strong> We use <code>buf</code> now but adopted it late. Early protos have inconsistent naming, missing field reservations, and enum values that don&rsquo;t follow the <code>TYPE_NAME_VALUE</code> pattern. Fixing these in a live system is painful.</p>
<p><strong>Start with <code>buf</code> instead of raw <code>protoc</code>.</strong> The <code>protoc</code> plugin ecosystem is a maze. <code>buf</code> handles code generation, linting, and breaking change detection in one tool. Should have started there.</p>
<p><strong>Don&rsquo;t  <a href="/blog/2016-01-15-why-microservices-arent-always-the-answer/"
   
   >over-decompose services</a>
.</strong> Our first instinct was one proto per entity. Node service. Deployment service. Billing service. Network service. That&rsquo;s fine in theory. In practice, most operations touch three or four services, so every user-facing action became a cascade of RPCs. We&rsquo;ve since consolidated the ones that always move together.</p>
<p>gRPC is a great tool for internal APIs. But it&rsquo;s a tool, not a religion. Use it where it helps. Use REST where that&rsquo;s simpler. The goal is shipping working software, not architectural purity.</p>
]]></content:encoded></item><item><title>Your Monolith Is Probably Fine</title><link>https://lawzava.com/blog/2019-07-01-microservices-migration-strategy/</link><pubDate>Mon, 01 Jul 2019 00:00:00 +0000</pubDate><guid>https://lawzava.com/blog/2019-07-01-microservices-migration-strategy/</guid><description>Most teams shouldn&amp;amp;rsquo;t be migrating to microservices. Here&amp;amp;rsquo;s how to tell if you actually should, and how to do it without wrecking your delivery for eighteen months.</description><content:encoded><![CDATA[<h2 id="quick-take">Quick take</h2>
<p>Stop. You probably don&rsquo;t need microservices. If you do, the strangler pattern is the only sane approach, your shared database is the real problem, and any team that can&rsquo;t operate a healthy monolith will absolutely drown in a distributed system.</p>
<p>I&rsquo;ve been building the backend for Decloud in Go since joining a deep-tech founder program earlier this year. Before that I ran engineering at a fintech startup and a mobility startup. Across all three, the microservices question came up. In two of those cases, the correct answer was &ldquo;not yet.&rdquo; Here&rsquo;s what I&rsquo;ve learned about knowing the difference.</p>
<h2 id="do-you-actually-have-a-monolith-problem">Do you actually have a monolith problem?</h2>
<p>Most teams that want microservices have a process problem dressed up as an architecture problem. Slow deploys, blocked releases, painful coordination &ndash; these feel like monolith issues, but usually they&rsquo;re symptoms of missing automation, weak tests, or unclear ownership.</p>
<p>Ask yourself:</p>
<ul>
<li>Are multiple teams genuinely blocked by shared release cycles? Not annoyed. Blocked.</li>
<li>Do different parts of your system need fundamentally different scaling or uptime guarantees?</li>
<li>Do clear product boundaries already exist in your codebase, or are you hoping microservices will create them?</li>
</ul>
<p>If you answered &ldquo;no&rdquo; to most of these, you don&rsquo;t need microservices. You need a better monolith. Invest in CI/CD, write tests, add feature flags, and stop doing synchronized deploys.</p>
<h2 id="the-uncomfortable-prerequisite">The uncomfortable prerequisite</h2>
<p>Here&rsquo;s the part nobody wants to hear. Before you can successfully run microservices, your monolith needs to already be healthy. Specifically:</p>
<ul>
<li>Automated tests that run fast and actually catch regressions</li>
<li>Deploys measured in minutes, not hours</li>
<li>Centralized logging and alerting that someone actually looks at</li>
<li>Clear module boundaries in the codebase</li>
<li>Feature flags for safe releases</li>
</ul>
<p>If you can&rsquo;t do these things with one service, you won&rsquo;t magically do them with twelve. Microservices multiply your operational surface area. Every gap becomes a canyon.</p>
<p>At the fintech startup we had a monolith handling market data, user portfolios, and news aggregation. The temptation to split was strong. But our deploy pipeline was slow and our test coverage had gaps. Splitting would have made both problems worse. We fixed the fundamentals first. Most of the pressure to split evaporated once deploys were fast and modules were cleanly separated.</p>
<h2 id="when-its-actually-time">When it&rsquo;s actually time</h2>
<p>Sometimes it&rsquo;s genuinely time. At Decloud, we&rsquo;re building a cloud infrastructure product where the billing service has completely different scaling characteristics than the provisioning engine. Billing needs to be rock-solid and auditable. Provisioning needs to be fast and horizontally scalable. Different teams own them. That&rsquo;s a real reason to split.</p>
<p>The strangler pattern is the only approach I&rsquo;d recommend:</p>
<pre tabindex="0"><code>Phase 1: All traffic hits the monolith
Phase 2: /billing routes to billing service, everything else stays
Phase 3: Most traffic hits services, small core remains
</code></pre><p>You extract one domain at a time. The rest stays stable. You can reverse any step. Progress is visible. This isn&rsquo;t glamorous work. It takes months. But it&rsquo;s the kind of work that actually ships.</p>
<h2 id="pick-your-first-extraction-carefully">Pick your first extraction carefully</h2>
<p>Your first service should be boring. Not your most critical business logic &ndash; that&rsquo;s your worst option. Pick something that:</p>
<ul>
<li>Has a small API surface</li>
<li>Changes frequently (so you see the benefits fast)</li>
<li>Has a clear owner</li>
<li>Has obvious scaling needs separate from the rest</li>
</ul>
<p>At the mobility startup, if we had gone the microservices route, the GPS tracking ingestion pipeline would have been the obvious first candidate. High throughput, clear boundary, independent scaling needs, and a single team owned it. The user management system? Terrible candidate. It touched everything.</p>
<h2 id="the-database-is-the-real-problem">The database is the real problem</h2>
<p>Everybody focuses on splitting code. The hard part is splitting data.</p>
<p>Shared databases are what turn &ldquo;microservices&rdquo; into a distributed monolith. If two services read from the same table, you haven&rsquo;t decoupled anything. You&rsquo;ve just added network hops to your coupling.</p>
<p>Before you extract a service:</p>
<ol>
<li>Map every table to its consumers. All of them.</li>
<li>Assign a single owner per dataset.</li>
<li>Expose shared data through an API, not a shared connection string.</li>
<li>Run dual-writes temporarily during migration. Emphasis on temporarily.</li>
</ol>
<p>A transitional read view can buy you time:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">VIEW</span> payments_order_read <span style="color:#66d9ef">AS</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> id, total_cents, currency, user_id, status
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> orders
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span> status <span style="color:#66d9ef">IN</span> (<span style="color:#e6db74">&#39;pending_payment&#39;</span>, <span style="color:#e6db74">&#39;paid&#39;</span>);
</span></span></code></pre></div><p>But set a date to kill it. Transitional things that don&rsquo;t have end dates become permanent things.</p>
<h2 id="integration-keep-it-boring">Integration: keep it boring</h2>
<p>For service-to-service communication, my strong preference is gRPC for synchronous calls and a durable message queue for async. In Go, the gRPC tooling is excellent and the generated clients mean one less thing to get wrong.</p>
<p>A few rules:</p>
<ul>
<li>Every sync call needs a timeout, a retry budget, and a circuit breaker. No exceptions.</li>
<li>Async handlers must be idempotent. You will get duplicate messages. Plan for it.</li>
<li>Request IDs for tracing. Everywhere. Non-negotiable.</li>
</ul>
<p>Skip the complex choreography patterns until your observability is genuinely mature. Start with simple orchestration. You can get clever later. You probably won&rsquo;t need to.</p>
<h2 id="how-you-know-it-worked">How you know it worked</h2>
<p>You&rsquo;ll know the migration is working when deployment frequency goes up per service, not when you have more services. More services with the same deploy cadence just means you added complexity for free.</p>
<p>Track lead time from commit to production. Track how often deploys fail and need rollback. Track incidents caused by cross-service dependencies. If those numbers aren&rsquo;t improving, the migration isn&rsquo;t helping regardless of how clean the architecture diagram looks.</p>
<p>Microservices can genuinely unlock velocity when the conditions are right. But &ldquo;our monolith feels messy&rdquo; isn&rsquo;t one of those conditions. Fix your house before you build an addition.</p>
]]></content:encoded></item><item><title>Istio: Powerful, Painful, and Probably More Than You Need</title><link>https://lawzava.com/blog/2018-11-26-service-mesh-istio-practical-guide/</link><pubDate>Mon, 26 Nov 2018 00:00:00 +0000</pubDate><guid>https://lawzava.com/blog/2018-11-26-service-mesh-istio-practical-guide/</guid><description>My honest take on evaluating Istio at the fintech startup — what it actually gives you, what it costs you, and why most teams should think twice before adopting it.</description><content:encoded><![CDATA[<p>I&rsquo;ll be honest: I wanted to hate Istio. We&rsquo;d been running microservices at the fintech startup for a while, and every few weeks someone would bring up service meshes like they were the answer to problems we hadn&rsquo;t even articulated yet. So I spent real time evaluating it. Deploying it. Fighting with it. And my conclusion is&hellip; complicated.</p>
<p>Istio is genuinely impressive technology. It&rsquo;s also a complexity bomb that most teams have no business adopting.</p>
<h2 id="what-it-actually-does">What it actually does</h2>
<p>The pitch is simple. You have microservices, they all need retries, timeouts, mTLS, and observability. Instead of implementing that in every service, you push it to a sidecar proxy layer. Istio manages those proxies. Fine.</p>
<p>In practice you get three things:</p>
<p><strong>Traffic control.</strong> Route by version, headers, percentage. Canary deployments become a YAML change instead of a code change.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">networking.istio.io/v1alpha3</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">VirtualService</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">api</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">hosts</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">api</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">http</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">route</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">destination</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">host</span>: <span style="color:#ae81ff">api</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">subset</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">weight</span>: <span style="color:#ae81ff">90</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">destination</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">host</span>: <span style="color:#ae81ff">api</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">subset</span>: <span style="color:#ae81ff">v2</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">weight</span>: <span style="color:#ae81ff">10</span>
</span></span></code></pre></div><p><strong>Mutual TLS without touching application code.</strong> This is genuinely nice. You flip a policy and suddenly everything&rsquo;s encrypted in transit.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">authentication.istio.io/v1alpha1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Policy</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">default</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">namespace</span>: <span style="color:#ae81ff">production</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">peers</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">mtls</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">mode</span>: <span style="color:#ae81ff">PERMISSIVE</span>
</span></span></code></pre></div><p><strong>Uniform observability.</strong> Every request goes through Envoy, so you get consistent metrics and traces everywhere. Request rates, error rates, latency percentiles, distributed traces — all without instrumenting each service individually.</p>
<p>That&rsquo;s the good stuff. And if you stopped reading here, you&rsquo;d think Istio is a no-brainer.</p>
<h2 id="how-the-thing-actually-works">How the thing actually works</h2>
<p>The architecture is a control plane plus a data plane. Data plane: Envoy sidecar proxies injected into every pod, intercepting all traffic. Control plane: four components — Pilot (routing), Mixer (policy and telemetry), Citadel (certificates), Galley (config processing).</p>
<pre tabindex="0"><code>App -&gt; Envoy -&gt; network -&gt; Envoy -&gt; App
</code></pre><p>Four control plane components for what is essentially a proxy configurator. That should tell you something about the operational surface area you&rsquo;re signing up for.</p>
<h2 id="the-part-nobody-talks-about-at-conferences">The part nobody talks about at conferences</h2>
<p>Here&rsquo;s what I found evaluating this at the fintech startup.</p>
<p>Every pod now has a sidecar. That&rsquo;s extra CPU, extra memory, extra things that can fail. We saw meaningful resource overhead. Not catastrophic, but not nothing — and it scales linearly with your pod count.</p>
<p>Timeout alignment is a nightmare. You set a 2-second timeout at the mesh layer, but the upstream service has a 5-second timeout, and the downstream expects responses in 1 second. Now you&rsquo;ve got three layers of timeout logic interacting in ways that are genuinely hard to reason about. We spent more time debugging timeout cascades than we saved by having mesh-level retries.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">networking.istio.io/v1alpha3</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">VirtualService</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">api</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">hosts</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">api</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">http</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">timeout</span>: <span style="color:#ae81ff">2s</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">retries</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">attempts</span>: <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">perTryTimeout</span>: <span style="color:#ae81ff">1s</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">route</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">destination</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">host</span>: <span style="color:#ae81ff">api</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">subset</span>: <span style="color:#ae81ff">v1</span>
</span></span></code></pre></div><p>Partial adoption is worse than no adoption. If half your services are in the mesh and half aren&rsquo;t, you&rsquo;ve got blind spots everywhere. Policies don&rsquo;t apply uniformly. Your observability has gaps. You&rsquo;re paying the complexity cost without getting the full benefit.</p>
<p>Egress traffic silently bypasses your policies unless you explicitly configure egress rules. We found this out the fun way.</p>
<p>And upgrades? Every Istio upgrade changes CRDs, defaults, sometimes both. You rehearse them or you regret it.</p>
<h2 id="when-its-actually-worth-it">When it&rsquo;s actually worth it</h2>
<p>Look, if you&rsquo;re running dozens of services on Kubernetes, you need consistent traffic policy across all of them, and you have the team to operate the mesh — Istio delivers. The RBAC model is solid:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">rbac.istio.io/v1alpha1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ServiceRole</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">api-reader</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">namespace</span>: <span style="color:#ae81ff">production</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">rules</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">api.production.svc.cluster.local</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">methods</span>: [<span style="color:#e6db74">&#34;GET&#34;</span>, <span style="color:#e6db74">&#34;POST&#34;</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">paths</span>: [<span style="color:#e6db74">&#34;/api/*&#34;</span>]
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">rbac.istio.io/v1alpha1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ServiceRoleBinding</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">api-reader-binding</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">namespace</span>: <span style="color:#ae81ff">production</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">subjects</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">user</span>: <span style="color:#e6db74">&#34;cluster.local/ns/production/sa/frontend&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">roleRef</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ServiceRole</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">api-reader</span>
</span></span></code></pre></div><p>The observability story — golden signals per service, distributed traces, service topology maps — is genuinely better than bolting together per-service monitoring.</p>
<p>But if you&rsquo;re running five services? Ten? Just write a shared library for retries and use Prometheus directly. You don&rsquo;t need this.</p>
<h2 id="my-actual-advice">My actual advice</h2>
<p>Start in staging. Not production. Not &ldquo;let&rsquo;s just try it on one production namespace.&rdquo; Staging.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl apply -f install/kubernetes/istio-demo.yaml
</span></span><span style="display:flex;"><span>kubectl label namespace staging istio-injection<span style="color:#f92672">=</span>enabled
</span></span></code></pre></div><p>Get comfortable with the abstractions. <code>DestinationRule</code> for defining service subsets, <code>VirtualService</code> for traffic splitting. Understand that these are two separate resources that reference each other and both need to be correct or nothing works.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">networking.istio.io/v1alpha3</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">DestinationRule</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">api</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">host</span>: <span style="color:#ae81ff">api</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">subsets</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">version</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">v2</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">version</span>: <span style="color:#ae81ff">v2</span>
</span></span></code></pre></div><p>Start with permissive mTLS. Watch your metrics. Only move to strict when you&rsquo;re confident nothing breaks. Then add RBAC rules one service pair at a time.</p>
<p>The teams I&rsquo;ve seen fail with Istio all did the same thing: they adopted everything at once because the demo looked cool. The teams that succeeded treated it like any other piece of infrastructure — incrementally, skeptically, with rollback plans and runbooks.</p>
<p>Istio is a powerful tool. Whether it&rsquo;s the right tool for you is a different question entirely. At the fintech startup, we got value from it — eventually. But &ldquo;eventually&rdquo; involved a lot of late nights and some colorful Slack messages that I won&rsquo;t reproduce here.</p>
]]></content:encoded></item><item><title>Securing Microservices: What Actually Works</title><link>https://lawzava.com/blog/2018-07-23-microservices-security-patterns/</link><pubDate>Mon, 23 Jul 2018 00:00:00 +0000</pubDate><guid>https://lawzava.com/blog/2018-07-23-microservices-security-patterns/</guid><description>You split the monolith. Now every service-to-service call is an attack surface. How I think about identity, authorization, encryption, and secrets management.</description><content:encoded><![CDATA[<h2 id="quick-take">Quick take</h2>
<p>Microservices don&rsquo;t have a security perimeter. They have dozens. Treat every internal hop as hostile, enforce identity everywhere, encrypt everything in transit, and keep secrets out of code. The rest is details.</p>
<p>When we broke the fintech startup&rsquo;s monolith into services, the first thing I noticed was how much implicit trust we had been leaning on. One process, one memory space, one set of credentials. Easy. Comfortable. Gone.</p>
<p>Microservices replace that single boundary with a mesh of network calls, each one a potential point of compromise. Every background job, every inter-service HTTP call, every gRPC stream &ndash; all of it&rsquo;s attack surface now. National cyber-defense drills one thing into you: assume the network is compromised. That mindset translates directly to microservices.</p>
<p>This post is what I wish I&rsquo;d had when we started. Practical patterns, not theory.</p>
<h2 id="start-with-a-threat-model">Start With a Threat Model</h2>
<p>Before writing a single line of security code, sit down and think about what can go wrong. Not in the abstract. Specifically.</p>
<p>At the fintech startup we built our threat model around a simple premise: any internal network segment can be observed or spoofed. Paranoid? Maybe. But it forces you to build real defenses instead of relying on the warm blanket of a private VPC.</p>
<p>The risks that keep me up at night:</p>
<ul>
<li>Stolen or replayed tokens granting access long after they should</li>
<li>Lateral movement &ndash; one compromised service becoming a beachhead into everything</li>
<li>Over-privileged service accounts that can read data they have no business touching</li>
<li>Sensitive data leaking through logs, traces, or error messages</li>
<li>Secrets baked into container images or checked into git</li>
</ul>
<p>If your threat model doesn&rsquo;t scare you a little, it&rsquo;s not honest enough.</p>
<h2 id="authentication-who-are-you">Authentication: Who Are You?</h2>
<h3 id="gateway-auth-for-external-traffic">Gateway Auth for External Traffic</h3>
<p>Centralize user authentication at the API gateway. Validate the token once, attach identity headers, forward to internal services over trusted channels. Done.</p>
<pre tabindex="0"><code>Client -&gt; API Gateway -&gt; Services
          validate token
          attach identity headers
</code></pre><p>This keeps individual services simple. They don&rsquo;t need to know about OAuth flows or token validation libraries. They just read a header. The critical trade-off: internal services must reject anything that didn&rsquo;t come through the gateway. If a service accepts direct traffic, you&rsquo;ve defeated the entire pattern.</p>
<h3 id="service-to-service-identity">Service-to-Service Identity</h3>
<p>This is where most teams get sloppy. Internal calls between services need identity too. &ldquo;It&rsquo;s internal&rdquo; isn&rsquo;t an authentication strategy.</p>
<p>Two options that work:</p>
<p><strong>Mutual TLS.</strong> Both sides present certificates from a trusted internal CA. Authenticates the caller, authenticates the receiver, encrypts the wire. This is the gold standard. We used it at the fintech startup for anything touching financial data.</p>
<p><strong>Short-lived service tokens.</strong> JWT or similar, with a tight expiry and explicit audience claim. Works well when you can&rsquo;t run a full mesh or when edge systems call internal APIs.</p>
<p>Pick one. Enforce it everywhere. No exceptions for &ldquo;low-risk&rdquo; services &ndash; those are the ones attackers pivot through.</p>
<h2 id="authorization-what-can-you-do">Authorization: What Can You Do?</h2>
<p>Authentication tells you who&rsquo;s calling. Authorization decides if they&rsquo;re allowed. These are separate concerns and you should keep them that way.</p>
<h3 id="centralized-policy-service">Centralized Policy Service</h3>
<p>A dedicated authorization service that evaluates allow/deny decisions. You send it &ldquo;service X wants to do Y on resource Z&rdquo; and it answers.</p>
<pre tabindex="0"><code>Service -&gt; AuthZ Service -&gt; Decision
</code></pre><p>Good for complex, frequently changing rules. Good for audit trails. Bad for latency-sensitive paths &ndash; you&rsquo;re adding a network hop to every decision. At the fintech startup we used this for anything involving user data access decisions. The latency hit was worth the consistency.</p>
<h3 id="embedded-policy-evaluation">Embedded Policy Evaluation</h3>
<p>Each service evaluates policy locally using a shared library or rules engine. No network hop. Fast. The downside is keeping policies in sync across dozens of services. One stale deployment and you&rsquo;ve got inconsistent authorization.</p>
<p>Use this for latency-critical paths where the rules are stable and well-understood.</p>
<h3 id="token-embedded-permissions">Token-Embedded Permissions</h3>
<p>Stuff roles or scopes into the JWT itself. Simple, no extra calls needed. But tokens are snapshots &ndash; if you revoke a permission, every unexpired token still carries the old grants. Keep expiry times short. Minutes, not hours.</p>
<p>Works for coarse-grained access control. Falls apart when you need fine-grained, data-specific rules.</p>
<h2 id="encrypt-everything-in-transit">Encrypt Everything in Transit</h2>
<p>Not just external traffic. All of it. Service-to-service, service-to-database, service-to-cache. Everything.</p>
<p>&ldquo;But it&rsquo;s a private network.&rdquo; I don&rsquo;t care. Private networks get breached. Network segmentation gets misconfigured. A single compromised host with tcpdump running will capture every unencrypted call in the segment.</p>
<p>Mutual TLS or a service mesh handles this with minimal code changes. Encryption alone doesn&rsquo;t replace authorization, but it kills passive eavesdropping and makes man-in-the-middle attacks dramatically harder.</p>
<h3 id="field-level-encryption-for-sensitive-data">Field-Level Encryption for Sensitive Data</h3>
<p>Some fields need protection beyond transport encryption. Payment card numbers, national IDs, health data &ndash; encrypt these at the application layer. If an intermediate proxy logs the request body or a tracing system captures the payload, the sensitive fields are still opaque.</p>
<p>We learned this the hard way at the fintech startup when a debug log captured a full API response including user financial preferences. Transport encryption didn&rsquo;t help because the log was written on the receiving end.</p>
<h2 id="secrets-management">Secrets Management</h2>
<p>Hardcoded secrets are a gift to attackers. Secrets in environment variables are only slightly better &ndash; they show up in process listings, crash dumps, and container inspection output.</p>
<p>What actually works:</p>
<ul>
<li>A real secrets manager. Vault, AWS Secrets Manager, whatever. Not a config file.</li>
<li>Short-lived credentials that expire before they can be exfiltrated and reused.</li>
<li>Rotation on a schedule and immediately after any incident.</li>
<li>No developer workstation has production secrets by default. Full stop.</li>
</ul>
<p>The running process fetches secrets at runtime. Nothing is baked into the image. Nothing lives in source control. If I can find your database password in a git history, your security posture is theater.</p>
<h2 id="defense-in-depth">Defense in Depth</h2>
<p>No single control is enough. Layer them.</p>
<h3 id="network-segmentation">Network Segmentation</h3>
<p>Default-deny between services. If service A doesn&rsquo;t need to talk to service B, block it. Use network policies based on service identity, not IP addresses. IPs change. Service names don&rsquo;t.</p>
<h3 id="input-validation-on-every-boundary">Input Validation on Every Boundary</h3>
<p>Even internal calls. Especially internal calls. A compromised service sending malformed data to a downstream service shouldn&rsquo;t be able to trigger a buffer overflow or SQL injection. Strict schemas, fail fast on anything unexpected.</p>
<p>This was hammered into us during national cyber-defense exercises. The perimeter isn&rsquo;t the only place attacks happen. Assume any input can be hostile.</p>
<h3 id="least-privilege-everywhere">Least Privilege Everywhere</h3>
<p>If a service only reads from a database table, its credentials shouldn&rsquo;t allow writes. If a service only calls two other services, its network policy should block everything else. Shared credentials across services are a lateral movement highway.</p>
<h3 id="resilience-controls-as-security">Resilience Controls as Security</h3>
<p>Circuit breakers, rate limits, and timeouts aren&rsquo;t just reliability features. They&rsquo;re security controls. A compromised service trying to exfiltrate data through a downstream API gets stopped by rate limits. A denial-of-service attempt gets contained by circuit breakers.</p>
<h2 id="observability-without-leaking">Observability Without Leaking</h2>
<p>Log authentication failures. Log authorization decisions. Log admin actions. Use consistent request IDs so you can trace a request across services during an incident.</p>
<p>But &ndash; and this matters &ndash; don&rsquo;t log the sensitive data itself. Log metadata. Redact by default. I&rsquo;ve seen security logging implementations that were themselves a data breach waiting to happen because they captured full request bodies &ldquo;for debugging.&rdquo;</p>
<p>The goal is answering four questions fast: who did what, when, and from where.</p>
<h2 id="what-id-tell-you-over-coffee">What I&rsquo;d Tell You Over Coffee</h2>
<p>Microservices security isn&rsquo;t a product you buy or a checklist you complete. It&rsquo;s a set of boring, consistent patterns applied everywhere. Mutual TLS, short-lived tokens, least privilege, secrets in a vault, encrypted transit, validated inputs, observable decisions.</p>
<p>None of this is glamorous. Most of it&rsquo;s plumbing. But I&rsquo;ve seen what happens when that plumbing leaks, both in military contexts and in production systems handling real user data. The organizations that stay safe are the ones that got the fundamentals right and kept them right, not the ones that bought the fanciest tools.</p>
<p>Build it into the platform so individual services get security by default. Make the secure path the easy path. That&rsquo;s the whole game.</p>
]]></content:encoded></item><item><title>Service Mesh: You Probably Don't Need One</title><link>https://lawzava.com/blog/2017-11-27-service-mesh-do-you-actually-need-one/</link><pubDate>Mon, 27 Nov 2017 00:00:00 +0000</pubDate><guid>https://lawzava.com/blog/2017-11-27-service-mesh-do-you-actually-need-one/</guid><description>I evaluated Istio and Linkerd for our microservices at the fintech startup. My conclusion: most teams are buying complexity they haven&amp;amp;rsquo;t earned yet.</description><content:encoded><![CDATA[<p>Everyone at KubeCon this year was talking about service meshes as if they were the next mandatory layer in your stack. Istio, Linkerd, Consul Connect. Sidecar proxies that magically handle retries, mTLS, traffic splitting, observability. I spent two weeks evaluating these tools for the fintech startup and walked away unconvinced.</p>
<p>Here&rsquo;s what a mesh actually does: it jams a proxy next to every service instance, routes all traffic through it, and lets a control plane push config to those proxies. You get consistent retries, mutual TLS without app changes, and automatic metrics collection. Sounds great on a slide deck.</p>
<p>The reality is uglier. Every sidecar eats memory and CPU. Tens of megabytes per pod, plus steady overhead. At the fintech startup, we run enough services that this cost isn&rsquo;t trivial, and we&rsquo;re not even that big. For a mobility startup &ndash; a side project with maybe eight services &ndash; it would be absurd. You&rsquo;re adding latency on every single request too. An extra hop. Maybe a few milliseconds. Doesn&rsquo;t sound like much until your p99 latency budget is already tight and you just burned a chunk of it on infrastructure plumbing.</p>
<p>Then there&rsquo;s the operational weight. A mesh isn&rsquo;t something you install and forget. It&rsquo;s a new control plane. New failure modes. When a request fails, is it the app? The mesh policy? The proxy? Good luck debugging that at 2am when your team is already stretched thin keeping Kubernetes itself stable.</p>
<p>I keep asking one question: <strong>what specific problem are you solving that you can&rsquo;t solve with something simpler?</strong></p>
<p>Need retries and circuit breaking? A small client library handles that. Need edge traffic control? An API gateway. Need service-to-service encryption? Application-level TLS works fine if you own the services. Need observability? Structured logging and an APM tool get you surprisingly far.</p>
<p>A mesh starts making sense when you have dozens of services, a complex communication graph, and genuine policy drift you can&rsquo;t manage any other way. Or when security compliance demands uniform mTLS and you physically can&rsquo;t retrofit every app. Those are real use cases. But most teams I talk to have 10-15 services and are adopting a mesh because it feels like the right thing to do. That&rsquo;s cargo culting.</p>
<p>If you&rsquo;re dead set on it, Linkerd is the saner choice right now. Smaller footprint, narrower scope, less to break. Istio is ambitious but heavy. Consul Connect makes sense if you&rsquo;re already deep in the HashiCorp ecosystem. But honestly? Start without any of them. Add the mesh when the pain is specific and measurable. Not before.</p>
<p>Keep it simple. You can always add complexity later. Removing it is the hard part.</p>
]]></content:encoded></item><item><title>Why We Went Event-Driven (and What Nearly Broke)</title><link>https://lawzava.com/blog/2017-04-10-building-event-driven-architectures/</link><pubDate>Mon, 10 Apr 2017 00:00:00 +0000</pubDate><guid>https://lawzava.com/blog/2017-04-10-building-event-driven-architectures/</guid><description>Lessons from building event-driven systems at the fintech startup and a mobility startup &amp;amp;ndash; what worked, what broke, and why I&amp;amp;rsquo;d do it again.</description><content:encoded><![CDATA[<p>At the fintech startup we had this price feed pipeline. Financial data from multiple exchanges, normalised, enriched, and pushed to users who had watchlists. The original design was request-response all the way down. User service calls price service, price service calls enrichment service, enrichment service calls the notification service. Neat diagram on a whiteboard. Terrible in production.</p>
<p>One Tuesday afternoon the enrichment service got slow. Not down. Slow. Response times climbed from 40ms to 1200ms. Because every upstream service was waiting synchronously, the entire chain backed up. Users stopped getting price alerts. The dashboard froze. We had a four-service outage caused by one service being a bit sluggish.</p>
<p>That was the week I started ripping out synchronous calls and replacing them with events.</p>
<h2 id="events-vs-commands--this-matters">Events vs. commands &ndash; this matters</h2>
<p>An event says &ldquo;this happened.&rdquo; Past tense. Immutable. <code>PriceUpdated</code>, <code>BikeUnlocked</code>, <code>UserRegistered</code>. A command says &ldquo;please do this.&rdquo; It can be rejected. It can fail.</p>
<p>The difference isn&rsquo;t pedantic. At a mobility startup, when a user unlocked a bike, we emitted a <code>BikeUnlocked</code> event. The billing service, the map service, the analytics pipeline &ndash; they all consumed that event independently. None of them could say &ldquo;no, don&rsquo;t unlock it.&rdquo; That decision was already made. They just reacted.</p>
<p>Commands flow differently. <code>ChargeCreditCard</code> can fail. <code>ReserveBike</code> can be rejected if inventory is zero. Mixing these up causes real bugs. I&rsquo;ve seen teams emit a <code>PaymentProcessed</code> event before the payment actually went through. Don&rsquo;t do that.</p>
<h2 id="the-broker-in-the-middle">The broker in the middle</h2>
<p>The whole thing works because of a message broker sitting between producers and consumers. We used Kafka at the fintech startup &ndash; those price feeds generated serious volume and we needed ordering guarantees per instrument. At the mobility startup it was RabbitMQ. Smaller scale, simpler ops, good enough.</p>
<p>The broker decouples everything. The price feed doesn&rsquo;t know or care that six different services consume its events. It publishes and moves on. Each consumer reads at its own pace. One goes down? The others keep running. You deploy a new analytics consumer next month? Just subscribe. The producer never changes.</p>
<p>This is the core win. Services stop depending on each other directly. They deploy independently. They fail independently. They scale independently.</p>
<h2 id="patterns-ive-actually-used">Patterns I&rsquo;ve actually used</h2>
<p><strong>Pub-sub</strong> is the bread and butter. At the fintech startup, a single <code>PriceUpdated</code> event triggered watchlist checks, portfolio recalculations, alert evaluations, and analytics writes. Four systems, zero direct calls between them.</p>
<p><strong>Event sourcing</strong> we used for audit-critical flows. Instead of storing &ldquo;current balance is X,&rdquo; you store every event that led to X. You can replay the whole history. You can answer &ldquo;what was the state at 3pm yesterday?&rdquo; without building a time-travel feature. The downside: schema evolution is painful, and once your event streams get long you need snapshots or replay takes forever.</p>
<p><strong>CQRS</strong> we adopted for the watchlist feature. Writes went through a command model &ndash; add stock, remove stock, set alert threshold. Reads came from a denormalised projection optimised for fast lookups. Different data shapes, different scaling needs. But you live with eventual consistency. A user adds a stock and for a few hundred milliseconds the read model hasn&rsquo;t caught up. You handle that in the UI or you accept it.</p>
<p><strong>Sagas</strong> we used at the mobility startup for the ride lifecycle. <code>BikeUnlocked</code> kicks off. If the ride ends normally, <code>RideCompleted</code> triggers billing. If something goes wrong &ndash; GPS lost, bike reported damaged &ndash; compensating events fire to reverse or adjust charges. No distributed transactions. Each step is its own event, each failure has a defined recovery path.</p>
<h2 id="where-the-real-work-lives">Where the real work lives</h2>
<p>Event design is everything. A bad event is one that forces the consumer to call back to the producer for context. At the fintech startup we learned this the hard way. Our first <code>PriceUpdated</code> event had just the instrument ID and the new price. The notification service needed the user&rsquo;s alert thresholds to decide whether to fire. So it called the user service. Synchronously. Defeating the entire point.</p>
<p>We fixed it by enriching the event. Not with everything, but with enough:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;event&#34;</span>: <span style="color:#e6db74">&#34;PriceUpdated&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;version&#34;</span>: <span style="color:#ae81ff">3</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;instrument_id&#34;</span>: <span style="color:#e6db74">&#34;AAPL&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;price&#34;</span>: <span style="color:#ae81ff">143.50</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;exchange&#34;</span>: <span style="color:#e6db74">&#34;NASDAQ&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;timestamp&#34;</span>: <span style="color:#e6db74">&#34;2017-04-09T14:30:00Z&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;previous_close&#34;</span>: <span style="color:#ae81ff">142.80</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Version the events from day one. You will change the schema. You will add fields, rename things, realise you forgot something critical. If you don&rsquo;t version, you break every consumer on every change.</p>
<p>Idempotency is non-negotiable. Kafka gives you at-least-once delivery. That means duplicates. Your consumers must handle processing the same event twice without corrupting state. Idempotency keys, deduplication windows, whatever works &ndash; but you can&rsquo;t skip this.</p>
<p>Dead letters. When a consumer fails to process an event, it has to go somewhere visible. Not silently dropped. Not retried infinitely until it blows up the queue. A dead letter topic with alerting. We caught a serialisation bug at the mobility startup within minutes because the dead letter queue spiked. Without it, we would have lost ride events silently.</p>
<h2 id="when-to-use-it-and-when-not-to">When to use it and when not to</h2>
<p>Go event-driven when multiple services need to react to the same change, when you need audit trails, when you want services to evolve independently, when eventual consistency is acceptable. Financial data pipelines? Perfect fit. IoT-style location updates from a fleet of bikes? Great.</p>
<p>Don&rsquo;t use it for simple CRUD apps. Don&rsquo;t use it if your team can&rsquo;t handle the operational overhead &ndash; you&rsquo;re running a broker now, monitoring consumer lag, managing schemas. And definitely don&rsquo;t use it if you need strict synchronous guarantees everywhere. A checkout flow where the user needs an immediate &ldquo;payment confirmed&rdquo; is a bad candidate for fire-and-forget events.</p>
<h2 id="what-id-tell-myself-before-starting">What I&rsquo;d tell myself before starting</h2>
<p>Pick the right broker for your volume and ordering needs. Kafka if you need partitioned ordering and high throughput. RabbitMQ if you want simpler operations and flexible routing. Don&rsquo;t overthink it early &ndash; you can migrate later, and you probably will.</p>
<p>Start with one bounded context. Don&rsquo;t try to event-drive the entire system in a quarter. We started with the price feed pipeline at the fintech startup. Got it stable. Understood the operational model. Then expanded.</p>
<p>The complexity doesn&rsquo;t disappear. It moves. Instead of debugging synchronous call chains, you&rsquo;re debugging event flows, consumer lag, and ordering anomalies. Different problems, not fewer problems. But the system bends instead of breaking, and at 2am that difference matters a lot.</p>
]]></content:encoded></item><item><title>Why Microservices Aren't Always the Answer</title><link>https://lawzava.com/blog/2016-01-15-why-microservices-arent-always-the-answer/</link><pubDate>Fri, 15 Jan 2016 00:00:00 +0000</pubDate><guid>https://lawzava.com/blog/2016-01-15-why-microservices-arent-always-the-answer/</guid><description>Most teams adopt microservices too early and pay for complexity they don&amp;amp;rsquo;t need yet. A well-structured monolith is faster, simpler, and keeps your options open.</description><content:encoded><![CDATA[<h2 id="quick-take">Quick take</h2>
<p>If you have fewer than four teams and your domain boundaries are still shifting, you almost certainly don&rsquo;t need microservices. You need a clean monolith and the discipline to keep it modular.</p>
<h3 id="the-split-that-cost-us-three-months">The split that cost us three months</h3>
<p>At a mobility startup we had a Go monolith that handled bike availability, user accounts, payments, and ride tracking. It was about 40k lines, well-tested, deployed in under two minutes. It worked.</p>
<p>Then we decided to extract payments into its own service. The reasoning sounded right: payments are sensitive, they have different scaling characteristics, and we wanted to isolate failures. Classic microservices pitch.</p>
<p>What actually happened: we spent three months building the extraction. We needed a new deploy pipeline, a message contract between services, retry logic for the network boundary, and a way to keep ride state consistent when the payment service was slow or down. We went from a function call that took microseconds to a network call that introduced latency, partial failures, and a new category of bugs we had never dealt with before.</p>
<p>The team was four engineers. We could have spent those three months shipping features. Instead we shipped infrastructure.</p>
<h3 id="the-real-cost-of-splitting-early">The real cost of splitting early</h3>
<p>Microservices solve an organizational problem, not a technical one. When you have multiple teams that need to ship independently without stepping on each other, services aligned to team boundaries are powerful. That&rsquo;s a real benefit.</p>
<p>But most teams I see adopting microservices don&rsquo;t have that problem. They have five to ten engineers, a shared codebase, and a product that&rsquo;s still changing shape weekly. At that stage, splitting into services means:</p>
<p><strong>More deploy pipelines to maintain.</strong> Each service needs its own CI, its own monitoring, its own alerting. That&rsquo;s real maintenance overhead for a small team.</p>
<p><strong>Distributed data pain.</strong> A query that was a simple SQL join becomes a cross-service call. A transaction that was ACID becomes an eventually consistent workflow. At the mobility startup, a single &ldquo;end ride and charge user&rdquo; operation went from one database transaction to a choreography of events across two services with compensating actions for failure cases. The code tripled in complexity.</p>
<p><strong>Testing gets hard.</strong> A monolith test suite runs in one process. Service tests require either mocking everything (which hides real bugs) or running integration environments that need their own care. We went from <code>go test ./...</code> taking 30 seconds to an integration suite that took 12 minutes and broke for infrastructure reasons as often as code reasons.</p>
<p><strong>Debugging gets hard.</strong> A stack trace in a monolith tells you exactly what happened. A distributed trace across services requires correlation IDs, centralized logging, and tracing infrastructure. In 2016, that tooling is still immature.</p>
<h3 id="when-a-monolith-is-the-right-call">When a monolith is the right call</h3>
<p>A monolith isn&rsquo;t a dirty word. A well-structured monolith with clear package boundaries, explicit interfaces between modules, and owned data per module gives you most of the architectural benefits of services without the operational tax.</p>
<p>At the fintech startup we kept the backend as a single deployable for much longer than conventional wisdom suggested. Financial news ingestion, NLP processing, user-facing API, all in one app. The key was strict internal boundaries. The NLP module exposed a clean interface. The API layer never reached into ingestion internals. Data ownership was explicit even though it shared a database.</p>
<p>This let us move fast. A new feature that touched multiple concerns was a single PR, a single deploy, a single rollback if something went wrong. We weren&rsquo;t coordinating releases across services or debugging network failures between components that used to be function calls.</p>
<h3 id="the-modular-monolith">The modular monolith</h3>
<p>The pattern I keep coming back to is the modular monolith. One deployable unit, strict internal boundaries, explicit interfaces between modules. You get the code organization benefits without paying the distributed systems tax.</p>
<p>The key disciplines:</p>
<ul>
<li>Each module owns its data. No reaching into another module&rsquo;s tables.</li>
<li>Modules communicate through defined interfaces, not by importing each other&rsquo;s internals.</li>
<li>Dependencies between modules are visible and intentional.</li>
</ul>
<p>This isn&rsquo;t easy. It requires code review discipline and a team that cares about boundaries. But it&rsquo;s dramatically simpler than operating a fleet of services, and it keeps the option to extract a service later when you have a concrete reason.</p>
<h3 id="when-you-actually-need-services">When you actually need services</h3>
<p>There are legitimate reasons to split. I&rsquo;ve seen three that hold up in practice:</p>
<p><strong>Radically different scaling needs.</strong> If one component handles 100x the traffic of everything else, separating it can save real money and improve reliability. At the mobility startup, the bike location tracking service eventually did need to be separate because it processed GPS updates at a rate that would have required over-provisioning the entire monolith.</p>
<p><strong>Independent team ownership.</strong> When you have genuinely separate teams that are blocked by coordinated releases, aligning service boundaries to team boundaries unblocks delivery. But this is a team problem, not a technology problem. If you have one team, you don&rsquo;t have this problem.</p>
<p><strong>Hard compliance boundaries.</strong> PCI, SOX, specific regulatory requirements that mandate isolation. These are real constraints, not aspirational architecture goals.</p>
<p>Notice what isn&rsquo;t on the list: &ldquo;because Netflix does it&rdquo; or &ldquo;because we might need to scale someday.&rdquo; Premature optimization applied to architecture is just as wasteful as premature optimization applied to code.</p>
<h3 id="if-youre-going-to-split-do-it-incrementally">If you&rsquo;re going to split, do it incrementally</h3>
<p>If you have a monolith and genuine reasons to extract a service, don&rsquo;t rewrite. Extract one piece. Run it alongside the monolith. Learn everything about operating two things instead of one. Then decide if the trade-off was worth it before extracting the next piece.</p>
<p>The operational foundations you need are the same ones that make a monolith healthy: good logging, monitoring, deployment automation, and incident response. Build those first. They pay off regardless of your architecture.</p>
<h3 id="the-bottom-line">The bottom line</h3>
<p>Microservices are a trade-off, not an upgrade. They buy organizational independence at the cost of operational complexity. For most teams I talk to, the honest answer is: you aren&rsquo;t big enough to need them yet, and your monolith isn&rsquo;t the thing slowing you down.</p>
<p>Ship features. Keep your boundaries clean. Split when the pain of coordination is real and measured, not hypothetical. That&rsquo;s the job.</p>
]]></content:encoded></item></channel></rss>