<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US">
  <title>DistributedByte</title>
  <subtitle>Personal notes on building software, data systems, and AI.</subtitle>
  <link href="https://distributedbyte.com/feed.xml" rel="self" type="application/atom+xml"/>
  <link href="https://distributedbyte.com/" rel="alternate" type="text/html"/>
  <id>https://distributedbyte.com/</id>
  <updated>2026-10-16T18:00:18+00:00</updated>
  <author><name>Azhar</name></author>
  
  <entry>
    <title>Testing behavior, not implementation</title>
    <link href="https://distributedbyte.com/articles/testing-the-behavior-not-the-implementation/" rel="alternate" type="text/html"/>
    <id>https://distributedbyte.com/articles/testing-the-behavior-not-the-implementation/</id>
    <published>2026-10-14T13:00:00+00:00</published>
    <updated>2026-10-14T13:00:00+00:00</updated>
    <category term="Software engineering"/>
    <summary>Tests that describe observable behavior survive refactoring; tests that mirror the implementation quietly make every change more expensive.</summary>
    <content type="html" xml:base="https://distributedbyte.com/articles/testing-the-behavior-not-the-implementation/">&lt;p&gt;A test suite earns its keep when it lets you change the code with confidence. A suite that fails every time you refactor, even though nothing a user could notice has changed, does the opposite: it teaches the team to distrust red builds and to avoid improving the design.&lt;/p&gt;

&lt;p&gt;The usual cause is not too many tests, but tests that are coupled to &lt;em&gt;how&lt;/em&gt; the code works instead of &lt;em&gt;what&lt;/em&gt; it promises.&lt;/p&gt;

&lt;h2 id=&quot;ask-what-a-caller-can-observe&quot;&gt;Ask what a caller can observe&lt;/h2&gt;

&lt;p&gt;Before writing a test, finish this sentence: “When a caller does X, they can see Y.” The answer is the behavior worth pinning down. Return values, persisted state, messages sent to other systems, and error responses all qualify. The order of private method calls, the number of times a helper ran, and the shape of an internal data structure do not.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Coupled to implementation: breaks if the discount logic is reorganized.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;priceCalculator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;applyDiscount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;taxCalculator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addTax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Coupled to behavior: survives any internal restructuring.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Money&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;108.00&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;checkout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;totalFor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;be-careful-with-mocks&quot;&gt;Be careful with mocks&lt;/h2&gt;

&lt;p&gt;Mocks are useful at real boundaries: a payment gateway, a message broker, a clock. Mocking your own collaborators inside the same module records the current wiring and little else. When you notice a test that is mostly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;when(...)&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verify(...)&lt;/code&gt;, consider using the real objects, or a small in-memory fake, and asserting on the outcome.&lt;/p&gt;

&lt;h2 id=&quot;prefer-fewer-wider-tests-over-many-narrow-ones&quot;&gt;Prefer fewer, wider tests over many narrow ones&lt;/h2&gt;

&lt;p&gt;Test through a public entry point, such as a service method or an HTTP handler, and let the internals vary freely. Reserve tiny unit tests for logic that is genuinely intricate, like parsing, calculations, and state machines. Wider tests are slower to write the first time, but they are cheaper to maintain because they only change when the behavior does.&lt;/p&gt;

&lt;h2 id=&quot;treat-a-painful-test-as-design-feedback&quot;&gt;Treat a painful test as design feedback&lt;/h2&gt;

&lt;p&gt;If a test needs a dozen collaborators to be constructed, the unit probably has too many responsibilities. If a behavior cannot be observed without reaching into private state, the interface may be hiding something callers actually need. Listen to that friction before reaching for a more powerful mocking tool.&lt;/p&gt;

&lt;p&gt;Good tests document intent, fail for the right reasons, and make refactoring boring. Aim for that, and the coverage number will mostly take care of itself.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Designing reliable event-driven systems</title>
    <link href="https://distributedbyte.com/articles/designing-reliable-event-driven-systems/" rel="alternate" type="text/html"/>
    <id>https://distributedbyte.com/articles/designing-reliable-event-driven-systems/</id>
    <published>2026-10-12T13:00:00+00:00</published>
    <updated>2026-10-12T13:00:00+00:00</updated>
    <category term="Distributed systems"/>
    <summary>A practical starting point for designing event-driven systems that remain understandable and resilient as they grow.</summary>
    <content type="html" xml:base="https://distributedbyte.com/articles/designing-reliable-event-driven-systems/">&lt;p&gt;Event-driven architecture can loosen the dependencies between services, but it does not make complexity disappear. It moves some of that complexity into message contracts, delivery guarantees, and the operational life of events.&lt;/p&gt;

&lt;p&gt;The most reliable designs begin with a clear answer to one question: &lt;strong&gt;what fact has happened that other parts of the system may care about?&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;model-facts-not-commands&quot;&gt;Model facts, not commands&lt;/h2&gt;

&lt;p&gt;An event describes something that already happened: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OrderPlaced&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PaymentCaptured&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ShipmentDispatched&lt;/code&gt;. A command asks a particular service to do something. Keeping those meanings distinct helps consumers react independently without pretending they own the producer’s workflow.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;eventId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;evt_01J9...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;OrderPlaced&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;occurredAt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2026-10-12T13:00:00Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;orderId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ord_4821&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;customerId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cus_193&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Include stable identifiers and a timestamp, and make the payload explicit. Consumers should not need to infer a business fact from a database row or an internal implementation detail.&lt;/p&gt;

&lt;h2 id=&quot;assume-delivery-can-repeat&quot;&gt;Assume delivery can repeat&lt;/h2&gt;

&lt;p&gt;Most practical brokers provide at-least-once delivery. A consumer can finish its work and fail before acknowledging the message, so the broker may deliver it again. Make handlers idempotent: processing the same event twice should have the same effect as processing it once.&lt;/p&gt;

&lt;p&gt;For example, record the event ID alongside a consumer’s durable result. A uniqueness constraint can turn a repeated delivery into a safe no-op.&lt;/p&gt;

&lt;h2 id=&quot;keep-the-database-and-event-in-step&quot;&gt;Keep the database and event in step&lt;/h2&gt;

&lt;p&gt;Writing a database change and publishing an event are two separate operations. If the process stops between them, the system can end up with a changed record and no event. The transactional outbox pattern stores the business change and an outgoing event in the same database transaction; a separate publisher forwards pending events and marks them delivered.&lt;/p&gt;

&lt;p&gt;This does not remove retries. It makes the source of truth recoverable and the failure mode visible.&lt;/p&gt;

&lt;h2 id=&quot;make-operations-part-of-the-design&quot;&gt;Make operations part of the design&lt;/h2&gt;

&lt;p&gt;Track consumer lag, processing failures, retry counts, and the age of the oldest unprocessed message. Define where poison messages go and how a human can inspect and replay them. A queue that accepts events but silently accumulates work is not a reliable system.&lt;/p&gt;

&lt;p&gt;Good event-driven architecture is less about adding a broker and more about making ownership, failure, and recovery explicit. Start with a small set of meaningful events, document their contracts, and let real boundaries—not fashion—determine where asynchronous communication helps.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>A practical guide to data pipeline reliability</title>
    <link href="https://distributedbyte.com/articles/a-practical-guide-to-data-pipeline-reliability/" rel="alternate" type="text/html"/>
    <id>https://distributedbyte.com/articles/a-practical-guide-to-data-pipeline-reliability/</id>
    <published>2026-10-08T13:00:00+00:00</published>
    <updated>2026-10-08T13:00:00+00:00</updated>
    <category term="Data engineering"/>
    <summary>Design data pipelines around observable guarantees, safe retries, and clear recovery paths.</summary>
    <content type="html" xml:base="https://distributedbyte.com/articles/a-practical-guide-to-data-pipeline-reliability/">&lt;p&gt;A pipeline is reliable when its users can tell what data is ready, how fresh it is, and what happens when a run fails. Successful jobs alone do not prove that the output is complete or correct.&lt;/p&gt;

&lt;h2 id=&quot;state-the-guarantee&quot;&gt;State the guarantee&lt;/h2&gt;

&lt;p&gt;Start by defining the contract: expected freshness, completeness, and acceptable delay. “Runs every hour” describes a schedule. “The dashboard reflects all events received by 10:00 within 15 minutes” describes a user-visible guarantee.&lt;/p&gt;

&lt;h2 id=&quot;make-retries-safe&quot;&gt;Make retries safe&lt;/h2&gt;

&lt;p&gt;Transient failures are normal. Prefer deterministic transformations and writes that can be repeated without duplicating records. For batch jobs, a partition can often be recomputed and replaced atomically. For streaming jobs, use stable event keys and explicit handling for late or malformed data.&lt;/p&gt;

&lt;h2 id=&quot;measure-the-data-too&quot;&gt;Measure the data, too&lt;/h2&gt;

&lt;p&gt;Monitor volume, freshness, schema changes, and key business invariants—not just process health. A job can finish successfully while producing an empty or unexpectedly small dataset. A few targeted checks often catch more meaningful failures than a long list of generic alerts.&lt;/p&gt;

&lt;h2 id=&quot;design-the-recovery-path&quot;&gt;Design the recovery path&lt;/h2&gt;

&lt;p&gt;Document how to identify affected partitions, replay input, and validate corrected output. Keep enough source history to support that recovery, and make ownership clear so an alert leads to action.&lt;/p&gt;

&lt;p&gt;Reliable data platforms make their guarantees visible and their failures repairable. That is a stronger foundation than assuming the pipeline will never break.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Evaluating retrieval-augmented generation</title>
    <link href="https://distributedbyte.com/articles/evaluating-retrieval-augmented-generation/" rel="alternate" type="text/html"/>
    <id>https://distributedbyte.com/articles/evaluating-retrieval-augmented-generation/</id>
    <published>2026-10-04T13:00:00+00:00</published>
    <updated>2026-10-04T13:00:00+00:00</updated>
    <category term="AI engineering"/>
    <summary>A useful RAG evaluation separates retrieval quality from answer quality and tests both against real questions.</summary>
    <content type="html" xml:base="https://distributedbyte.com/articles/evaluating-retrieval-augmented-generation/">&lt;p&gt;When a retrieval-augmented generation system gives a poor answer, the cause may be missing evidence, weak ranking, or a model that fails to use the evidence it received. Looking only at the final response makes those problems hard to distinguish.&lt;/p&gt;

&lt;h2 id=&quot;build-a-representative-question-set&quot;&gt;Build a representative question set&lt;/h2&gt;

&lt;p&gt;Collect real questions users ask, including ambiguous questions, questions with no answer in the corpus, and questions whose answer depends on a particular source. Keep the expected evidence and a short reference answer for each one.&lt;/p&gt;

&lt;h2 id=&quot;score-retrieval-and-generation-separately&quot;&gt;Score retrieval and generation separately&lt;/h2&gt;

&lt;p&gt;For retrieval, measure whether the needed passages appear near the top of the results. For generation, check whether the answer is supported by those passages, answers the question, and abstains when evidence is absent. Human review remains useful for judging whether a response is genuinely helpful.&lt;/p&gt;

&lt;h2 id=&quot;evaluate-changes-as-a-system&quot;&gt;Evaluate changes as a system&lt;/h2&gt;

&lt;p&gt;Compare chunking, metadata filters, embedding models, rerankers, prompts, and model versions against the same evaluation set. Track latency and cost alongside quality. A small improvement in answer quality may not justify a large increase in latency for every use case.&lt;/p&gt;

&lt;p&gt;The goal is not one universal RAG score. It is a repeatable way to see which stage improved, which regressed, and whether the change matters to the people using the system.&lt;/p&gt;
</content>
  </entry>
  
</feed>
