<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>System-Design on Corebaseit — Design System Engineering · POS · Payments · Security</title><link>https://corebaseit.com/tags/system-design/</link><description>Recent content in System-Design on Corebaseit — Design System Engineering · POS · Payments · Security</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>contact@corebaseit.com (Vincent Bevia)</managingEditor><webMaster>contact@corebaseit.com (Vincent Bevia)</webMaster><lastBuildDate>Mon, 24 Aug 2026 00:00:00 +0200</lastBuildDate><atom:link href="https://corebaseit.com/tags/system-design/index.xml" rel="self" type="application/rss+xml"/><item><title>Spike First, ADR Second: Evidence and Rationale in System Design</title><link>https://corebaseit.com/corebaseit_posts/spike-first-adr-second/</link><pubDate>Mon, 24 Aug 2026 00:00:00 +0200</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/corebaseit_posts/spike-first-adr-second/</guid><description>&lt;img src="https://corebaseit.com/diagrams/Spike_and_ADR_Decision_Loop.png" alt="Featured image of post Spike First, ADR Second: Evidence and Rationale in System Design" />&lt;p>System design discussions often collapse into technology comparisons: Kafka or SQS, PostgreSQL or DynamoDB, synchronous or asynchronous, monolith or microservices. Those choices matter. They are not the whole problem.&lt;/p>
&lt;p>Architectural work has two other obligations that diagrams do not carry by themselves:&lt;/p>
&lt;ol>
&lt;li>How did the team gather enough evidence to decide?&lt;/li>
&lt;li>Why was this option chosen over the alternatives that were still on the table?&lt;/li>
&lt;/ol>
&lt;p>Two lightweight practices address those obligations: &lt;strong>Spikes&lt;/strong> and &lt;strong>Architecture Decision Records (ADRs)&lt;/strong>. They solve different problems. Used together under uncertainty, they form a simple decision loop — investigate, decide, document — that keeps future teams from treating intentional trade-offs as accidents.&lt;/p>
&lt;p>This post defines each practice, shows how they connect, and grounds the pattern in a payment-system example: asynchronous refund processing with external partner latency, retries, and idempotency. The POS framing draws on &lt;a class="link" href="https://corebaseit.com/my-books/" target="_blank" rel="noopener"
>&lt;em>POINT OF SALE ARCHITECTURE&lt;/em>&lt;/a> (&lt;em>the book&lt;/em>), especially Chapter 6 (architectural principles and separation of concerns), Chapter 14 (offline / store-and-forward queues, retries, idempotency), and Chapter 15 (backend timeouts, safe retries, STAN/RRN correlation).&lt;/p>
&lt;p>Related reading: &lt;a class="link" href="https://corebaseit.com/corebaseit_posts/double-charging/" >double charging and idempotency&lt;/a>, &lt;a class="link" href="https://corebaseit.com/corebaseit_posts/offline-emv-vs-store-and-forward/" >offline EMV vs store-and-forward&lt;/a>.&lt;/p>
&lt;h2 id="claims-to-keep-separate">Claims to keep separate
&lt;/h2>&lt;p>&lt;strong>Confirmed practice and sources:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>An ADR is a short record of an architecturally significant decision, typically covering context, decision, and consequences (Michael Nygard, 2011; Thoughtworks Technology Radar — Lightweight Architecture Decision Records).&lt;/li>
&lt;li>A Spike (from Extreme Programming) is a time-boxed investigation used to reduce technical uncertainty before committing to an implementation path.&lt;/li>
&lt;li>Payment backends that retry across unreliable partners must assume duplicate delivery and enforce idempotency; store-and-forward and authorization retries make this explicit (&lt;em>the book&lt;/em>, Ch. 14–15).&lt;/li>
&lt;li>Architecture is largely trade-off analysis among competing characteristics, not selection of a universally “best” technology (Richards &amp;amp; Ford, &lt;em>Fundamentals of Software Architecture&lt;/em>).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Interpretation (practitioner method):&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>“Spike first, ADR second” is a useful default under uncertainty. It is not a rule that every ADR requires a Spike, or that every Spike must produce an ADR.&lt;/li>
&lt;li>The refund / SQS example below is illustrative. Your broker, ordering needs, and partner SLAs will differ; the method transfers, the product choice may not.&lt;/li>
&lt;/ul>
&lt;h2 id="architecture-contains-uncertainty">Architecture contains uncertainty
&lt;/h2>&lt;p>Consider a refund processing path where an external GDS or airline API can be slow or temporarily unavailable:&lt;/p>
&lt;p style="text-align: center;">
&lt;a href="https://corebaseit.com/diagrams/Spike first, ADR second 1.png" target="_blank" rel="noopener">
&lt;img src="https://corebaseit.com/diagrams/Spike first, ADR second 1.png" alt="Spike first, ADR second 1" style="max-width: 720px; width: 100%; height: auto;" />
&lt;/a>
&lt;/p>
&lt;p>Asynchronous processing is a reasonable starting assumption: the external system should not block the merchant-facing API indefinitely. That assumption does not answer the next layer of questions:&lt;/p>
&lt;ul>
&lt;li>Managed queue (for example SQS) or log-oriented broker (for example Kafka)?&lt;/li>
&lt;li>What ordering guarantees are actually required — global, per refund, or none?&lt;/li>
&lt;li>Can messages be delivered more than once?&lt;/li>
&lt;li>How do retries, dead-letter queues, and worker crashes interact?&lt;/li>
&lt;li>Is replay a real requirement for this workflow, or only an attractive feature?&lt;/li>
&lt;li>What throughput and latency budget does the partner path impose?&lt;/li>
&lt;/ul>
&lt;p>Drawing another box on the diagram papers over those unknowns. Naming the unknowns is more useful: the team does not know yet. That is the entry condition for a Spike.&lt;/p>
&lt;p>In POS systems the same pattern appears whenever the stack sits between a deterministic payment state machine and an unreliable upstream — store-and-forward sync, scheme timeouts, late issuer responses, and duplicate suppression via STAN/RRN or dedicated idempotency keys (&lt;em>the book&lt;/em>, Ch. 14–15; see also &lt;a class="link" href="https://corebaseit.com/corebaseit_posts/double-charging/" >double charging&lt;/a>).&lt;/p>
&lt;h2 id="spike-reduce-uncertainty-before-committing">Spike: reduce uncertainty before committing
&lt;/h2>&lt;p>A Spike is a &lt;strong>time-boxed technical investigation&lt;/strong> aimed at a specific question. It is not the production implementation. It may include a small proof of concept, a benchmark, an integration test against a partner sandbox, latency or throughput measurement, or structured comparison of two or three alternatives.&lt;/p>
&lt;p>The Spike starts with a question, for example:&lt;/p>
&lt;blockquote>
&lt;p>Can a managed FIFO/work-queue service meet the ordering, retry, and throughput requirements of our refund processing path, if consumers are designed for at-least-once delivery?&lt;/p>&lt;/blockquote>
&lt;p>Give the investigation a hard time box (often one to three days). Structure the work so findings are reusable:&lt;/p>
&lt;p style="text-align: center;">
&lt;a href="https://corebaseit.com/diagrams/Spike first ADR second 2.png" target="_blank" rel="noopener">
&lt;img src="https://corebaseit.com/diagrams/Spike first ADR second 2.png" alt="Spike first ADR second 2" style="max-width: 720px; width: 100%; height: auto;" />
&lt;/a>
&lt;/p>
&lt;p>A useful Spike ends with evidence, not preference. Typical findings for this kind of path:&lt;/p>
&lt;ul>
&lt;li>Throughput is adequate for projected refund volume.&lt;/li>
&lt;li>Ordering is required only per refund identifier, not globally.&lt;/li>
&lt;li>Duplicate delivery must be assumed; workers need idempotent handlers.&lt;/li>
&lt;li>Retries plus a dead-letter path cover the partner failure modes in scope.&lt;/li>
&lt;li>Log replay is attractive but unnecessary for this workflow’s recovery model.&lt;/li>
&lt;/ul>
&lt;p>The decision can now rest on measured constraints. Months later, though, someone will still ask why the queue technology is what it is. Evidence that lived only in a chat thread or a demo branch will be gone. That is the ADR’s job.&lt;/p>
&lt;h2 id="adr-preserve-the-reasoning-behind-the-decision">ADR: preserve the reasoning behind the decision
&lt;/h2>&lt;p>An &lt;strong>Architecture Decision Record&lt;/strong> is a short document that records one architecturally significant decision and the forces around it. Nygard’s original shape is Status, Context, Decision, and Consequences. Many teams add Alternatives Considered; that extension is useful in payment systems where several brokers or sync models are always in play.&lt;/p>
&lt;p>A lightweight example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>ADR-017: Use a managed work queue for refund processing
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Accepted
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Context
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Refund processing is asynchronous. The external GDS can be slow or
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>unavailable. Workers can crash mid-flight. At-least-once delivery
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>must be assumed; duplicate refund side effects are unacceptable.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Decision
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Use a managed work queue (SQS-class) for refund jobs, with
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>idempotent consumers keyed by refund id (and STAN/RRN where the
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>payment network is involved).
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Alternatives considered
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- Log-oriented broker (Kafka-class) for replay-first design
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- Self-managed broker (RabbitMQ-class)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- Synchronous refund calls from the API
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Consequences
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+ Managed operations and built-in retry / DLQ patterns
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+ Simple worker model aligned to job processing
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- Consumers must be idempotent under duplicate delivery
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- Weaker fit if full event replay becomes a primary requirement
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The decision line alone is cheap; the architecture diagram already shows the queue. The durable value is context, alternatives, and accepted downsides. Without that, a later engineer can “fix” an intentional trade-off — for example by introducing a replay-heavy broker when the original Spike showed replay was out of scope.&lt;/p>
&lt;h2 id="spike--adr-as-a-loop">Spike + ADR as a loop
&lt;/h2>&lt;p style="text-align: center;">
&lt;a href="https://corebaseit.com/diagrams/Spike first ADR second 3.png" target="_blank" rel="noopener">
&lt;img src="https://corebaseit.com/diagrams/Spike first ADR second 3.png" alt="Spike first ADR second 3" style="max-width: 720px; width: 100%; height: auto;" />
&lt;/a>
&lt;/p>
&lt;p>Compact form:&lt;/p>
&lt;blockquote>
&lt;p>Spike = investigate.&lt;br>
ADR = decide and document.&lt;/p>&lt;/blockquote>
&lt;p>A Spike does not always produce an ADR. An ADR does not always require a Spike. Some decisions are constrained enough that experimentation would waste calendar time. When uncertainty is material — partner behaviour, ordering, duplicate risk, certification impact — the combination is hard to beat on cost relative to the confusion it prevents.&lt;/p>
&lt;h2 id="why-diagrams-are-not-enough">Why diagrams are not enough
&lt;/h2>&lt;p>System diagrams show &lt;strong>what exists&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>API → queue → workers → PostgreSQL / partner API
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>They do not naturally show &lt;strong>why it exists&lt;/strong>: why this broker, why asynchronous rather than synchronous, why pessimistic rather than optimistic concurrency, why REST rather than an event bus for a given boundary.&lt;/p>
&lt;p>A working architecture therefore has a second layer behind the boxes:&lt;/p>
&lt;p style="text-align: center;">
&lt;a href="https://corebaseit.com/diagrams/Spike first ADR second 4.png" target="_blank" rel="noopener">
&lt;img src="https://corebaseit.com/diagrams/Spike first ADR second 4.png" alt="Spike first ADR second 4" style="max-width: 720px; width: 100%; height: auto;" />
&lt;/a>
&lt;/p>
&lt;p>In POS application design, Chapter 6’s principles — separation of concerns, deterministic state management, security by design, targeted observability — are the kinds of constraints that should appear in ADR context when a team chooses how refunds, reversals, or store-and-forward sync are structured (&lt;em>the book&lt;/em>, Ch. 6, 14).&lt;/p>
&lt;h2 id="architecture-is-trade-off-work">Architecture is trade-off work
&lt;/h2>&lt;p>Richards and Ford argue that architecture is fundamentally about understanding and balancing trade-offs. There is rarely a universally correct choice. A log-oriented broker is not simply “better” than a work queue. Microservices are not simply “better” than a modular monolith. Event-driven flows are not simply “better” than synchronous request/response.&lt;/p>
&lt;p>Each option improves some characteristics and degrades others. The productive question shifts from “What is the best technology?” to “Given our requirements and constraints, which trade-offs are we willing to accept?”&lt;/p>
&lt;p>Spikes surface those trade-offs under load, latency, and failure. ADRs make the accepted set explicit. &lt;em>Software Architecture: The Hard Parts&lt;/em> pushes the same style into decomposition, data ownership, coupling, orchestration versus choreography, and evolution — problems that rarely have perfect answers, only recorded compromises.&lt;/p>
&lt;p>Payment systems sharpen the stakes: a wrong retry policy can double-post; a missing idempotency key can create chargebacks and reconciliation debt; a store-and-forward design that ignores scheme and liability constraints can pass a demo and fail certification (&lt;em>the book&lt;/em>, Ch. 14–15).&lt;/p>
&lt;h2 id="keep-both-practices-light">Keep both practices light
&lt;/h2>&lt;p>Neither practice needs bureaucracy.&lt;/p>
&lt;ul>
&lt;li>A Spike answers a question inside a time box. It should not become an open-ended research programme.&lt;/li>
&lt;li>An ADR preserves a decision in a page or two. It should not become a 40-page design specification.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>Question → Spike → Evidence → Trade-offs → Decision → ADR → Architecture
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Months later, when someone asks why the system looks the way it does, the answer need not be tribal memory about an engineer who left. The reasoning is still in the repository, next to the code that implements it.&lt;/p>
&lt;h2 id="references">References
&lt;/h2>&lt;ol>
&lt;li>Nygard, M. — “Documenting Architecture Decisions,” Cognitect blog, 15 Nov 2011. &lt;a class="link" href="https://www.cognitect.com/blog/2011/11/15/documenting-architecture-decisions" target="_blank" rel="noopener"
>https://www.cognitect.com/blog/2011/11/15/documenting-architecture-decisions&lt;/a>&lt;/li>
&lt;li>Thoughtworks Technology Radar — Lightweight Architecture Decision Records (Adopt). &lt;a class="link" href="https://www.thoughtworks.com/radar/techniques/lightweight-architecture-decision-records" target="_blank" rel="noopener"
>https://www.thoughtworks.com/radar/techniques/lightweight-architecture-decision-records&lt;/a>&lt;/li>
&lt;li>Fowler, M. — “Architecture Decision Record” (bliki). &lt;a class="link" href="https://martinfowler.com/bliki/ArchitectureDecisionRecord.html" target="_blank" rel="noopener"
>https://martinfowler.com/bliki/ArchitectureDecisionRecord.html&lt;/a>&lt;/li>
&lt;li>Richards, M. &amp;amp; Ford, N. — &lt;em>Fundamentals of Software Architecture: An Engineering Approach&lt;/em> (2nd ed.). O’Reilly.&lt;/li>
&lt;li>Ford, N., Richards, M., Sadalage, P. &amp;amp; Dehghani, Z. — &lt;em>Software Architecture: The Hard Parts&lt;/em>. O’Reilly.&lt;/li>
&lt;li>Extreme Programming / Kent Beck — Spike Solution (time-boxed investigation before committing to an implementation).&lt;/li>
&lt;li>Bevia, V. — &lt;em>Point-of-Sale Systems Architecture&lt;/em>, Chapters 6, 14, and 15: &lt;a class="link" href="https://corebaseit.com/my-books/" target="_blank" rel="noopener"
>https://corebaseit.com/my-books/&lt;/a>&lt;/li>
&lt;li>Corebaseit — &lt;a class="link" href="https://corebaseit.com/corebaseit_posts/double-charging/" >Double charging&lt;/a>; &lt;a class="link" href="https://corebaseit.com/corebaseit_posts/offline-emv-vs-store-and-forward/" >Offline EMV vs store-and-forward&lt;/a>.&lt;/li>
&lt;/ol></description></item></channel></rss>