<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Deal-Engines on Corebaseit — Design System Engineering · POS · Payments · Security</title><link>https://corebaseit.com/deal-engine/</link><description>Recent content in Deal-Engines 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><atom:link href="https://corebaseit.com/deal-engine/index.xml" rel="self" type="application/rss+xml"/><item><title/><link>https://corebaseit.com/deal-engine/adr-0001/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/deal-engine/adr-0001/</guid><description>&lt;h1 id="adr-0001-use-apache-kafka-for-inter-service-communication">ADR-0001: Use Apache Kafka for Inter-Service Communication
&lt;/h1>&lt;h2 id="status">Status
&lt;/h2>&lt;p>Accepted&lt;/p>
&lt;h2 id="date">Date
&lt;/h2>&lt;p>2025-11-15&lt;/p>
&lt;h2 id="context">Context
&lt;/h2>&lt;p>Our platform has grown from 3 microservices to 12 over the past year.
Services currently communicate synchronously via HTTP REST calls. This
creates several problems:&lt;/p>
&lt;ul>
&lt;li>Cascade failures: when the inventory service is down, the order
service can&amp;rsquo;t process orders, even though inventory checks could
be eventual.&lt;/li>
&lt;li>Tight coupling: services need to know each other&amp;rsquo;s API contracts
and endpoints.&lt;/li>
&lt;li>Performance bottlenecks: some operations trigger chains of 4-5
synchronous calls, adding latency.&lt;/li>
&lt;/ul>
&lt;p>We process approximately 100K events per day. We expect this to grow
to 500K within 12 months. The team has 8 backend engineers, 2 of whom
have prior Kafka experience.&lt;/p>
&lt;h2 id="decision">Decision
&lt;/h2>&lt;p>We will adopt Apache Kafka as the primary mechanism for asynchronous
inter-service communication. Synchronous HTTP will remain for
request/response patterns where the caller needs an immediate result
(e.g., authentication checks).&lt;/p>
&lt;p>We will use Confluent Cloud as the managed Kafka provider to minimize
operational overhead.&lt;/p>
&lt;h2 id="alternatives-considered">Alternatives Considered
&lt;/h2>&lt;h3 id="rabbitmq">RabbitMQ
&lt;/h3>&lt;ul>
&lt;li>Pros: Simpler to operate, lower learning curve, supports multiple
messaging patterns (pub/sub, point-to-point, routing).&lt;/li>
&lt;li>Cons: Less suitable for event sourcing patterns we plan to adopt.
Weaker replay/rewind capabilities. Community momentum has shifted
toward Kafka for event-driven architectures.&lt;/li>
&lt;li>Why rejected: We anticipate needing event replay for audit and
debugging. Kafka&amp;rsquo;s log-based architecture is better suited.&lt;/li>
&lt;/ul>
&lt;h3 id="aws-sqs--sns">AWS SQS + SNS
&lt;/h3>&lt;ul>
&lt;li>Pros: Fully managed, no infrastructure to maintain, tight AWS
integration.&lt;/li>
&lt;li>Cons: Vendor lock-in to AWS. Limited message ordering guarantees.
No built-in stream processing (would need Kinesis or Lambda).
Higher per-message cost at our projected volume.&lt;/li>
&lt;li>Why rejected: We want to avoid deepening AWS lock-in, and we need
ordered message delivery for financial events.&lt;/li>
&lt;/ul>
&lt;h3 id="keep-synchronous-http-with-circuit-breakers">Keep Synchronous HTTP (with circuit breakers)
&lt;/h3>&lt;ul>
&lt;li>Pros: No new infrastructure. Team already familiar. Circuit
breakers address cascade failures.&lt;/li>
&lt;li>Cons: Doesn&amp;rsquo;t solve tight coupling. Latency still accumulates
across call chains. Circuit breakers are a band-aid, not a
solution to the fundamental coupling problem.&lt;/li>
&lt;li>Why rejected: Addresses symptoms, not root cause.&lt;/li>
&lt;/ul>
&lt;h2 id="consequences">Consequences
&lt;/h2>&lt;h3 id="positive">Positive
&lt;/h3>&lt;ul>
&lt;li>Services become decoupled: producers don&amp;rsquo;t need to know consumers.&lt;/li>
&lt;li>Cascade failures eliminated for async workflows.&lt;/li>
&lt;li>Event replay enables powerful debugging and audit capabilities.&lt;/li>
&lt;li>Foundation for event sourcing patterns in future services.&lt;/li>
&lt;/ul>
&lt;h3 id="negative">Negative
&lt;/h3>&lt;ul>
&lt;li>Operational complexity increases (Kafka cluster, schema registry,
consumer group management). Mitigated by using Confluent Cloud.&lt;/li>
&lt;li>Team needs training on Kafka concepts and patterns.&lt;/li>
&lt;li>Eventual consistency replaces strong consistency for async flows.
Some workflows need redesign.&lt;/li>
&lt;li>Debugging distributed async flows is harder than tracing synchronous
HTTP calls. We&amp;rsquo;ll need distributed tracing (see ADR-0024).&lt;/li>
&lt;/ul>
&lt;h3 id="risks">Risks
&lt;/h3>&lt;ul>
&lt;li>If message volume exceeds Confluent Cloud pricing tiers, costs
could increase significantly. Monitor and set alerts.&lt;/li>
&lt;li>Schema evolution across services requires discipline. Plan to
adopt Avro with schema registry.&lt;/li>
&lt;/ul>
&lt;h2 id="related-decisions">Related Decisions
&lt;/h2>&lt;ul>
&lt;li>ADR-0024: Adopt OpenTelemetry for Distributed Tracing&lt;/li>
&lt;li>ADR-0018: Service Communication Contracts (superseded by this ADR)&lt;/li>
&lt;/ul></description></item><item><title/><link>https://corebaseit.com/deal-engine/adr-0002/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/deal-engine/adr-0002/</guid><description>&lt;h1 id="adr-0002-use-aws-sqs-for-asynchronous-inter-service-communication-from-chatgpt">ADR-0002: Use AWS SQS for Asynchronous Inter-Service Communication. (from ChatGPT)
&lt;/h1>&lt;h2 id="status">Status
&lt;/h2>&lt;p>Accepted&lt;/p>
&lt;h2 id="date">Date
&lt;/h2>&lt;p>2026-08-31&lt;/p>
&lt;h2 id="context">Context
&lt;/h2>&lt;p>As Deal Engine extracts capabilities from the existing monolith into independent services, some workflows no longer require synchronous request/response communication.&lt;/p>
&lt;p>Operations such as refund processing may involve slow or temporarily unavailable external systems such as GDS and airline APIs. Using synchronous HTTP for these workflows creates runtime coupling: if the downstream service is unavailable or slow, the upstream service is also affected.&lt;/p>
&lt;p>Deal Engine already operates its infrastructure primarily on AWS. We need a reliable asynchronous communication mechanism that integrates with the existing platform without introducing unnecessary operational complexity.&lt;/p>
&lt;h2 id="decision">Decision
&lt;/h2>&lt;p>We will use &lt;strong>Amazon SQS&lt;/strong> as the default mechanism for asynchronous point-to-point communication between services.&lt;/p>
&lt;p>Synchronous HTTP will remain for operations where the caller requires an immediate response.&lt;/p>
&lt;p>Consumers must assume &lt;strong>at-least-once delivery&lt;/strong> and therefore be designed to handle duplicate messages safely.&lt;/p>
&lt;h2 id="alternatives-considered">Alternatives Considered
&lt;/h2>&lt;h3 id="apache-kafka">Apache Kafka
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>Pros:&lt;/strong> High throughput, durable event log, strong replay capabilities, stream processing ecosystem.&lt;/li>
&lt;li>&lt;strong>Cons:&lt;/strong> Higher operational and conceptual complexity than required for our current workloads.&lt;/li>
&lt;li>&lt;strong>Why rejected:&lt;/strong> We primarily need reliable asynchronous work distribution, not event streaming or long-term event replay.&lt;/li>
&lt;/ul>
&lt;h3 id="rabbitmq">RabbitMQ
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>Pros:&lt;/strong> Mature messaging platform with flexible routing capabilities.&lt;/li>
&lt;li>&lt;strong>Cons:&lt;/strong> Introduces another infrastructure technology and operational model.&lt;/li>
&lt;li>&lt;strong>Why rejected:&lt;/strong> SQS provides the required queueing capabilities and integrates naturally with our existing AWS infrastructure.&lt;/li>
&lt;/ul>
&lt;h3 id="synchronous-http">Synchronous HTTP
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>Pros:&lt;/strong> Simple request/response model and immediate results.&lt;/li>
&lt;li>&lt;strong>Cons:&lt;/strong> Runtime coupling, failure propagation, and poor fit for long-running external operations.&lt;/li>
&lt;li>&lt;strong>Why rejected:&lt;/strong> It remains appropriate for synchronous workflows but not as the default for asynchronous processing.&lt;/li>
&lt;/ul>
&lt;h2 id="consequences">Consequences
&lt;/h2>&lt;h3 id="positive">Positive
&lt;/h3>&lt;ul>
&lt;li>Services are less runtime-coupled.&lt;/li>
&lt;li>Temporary consumer outages do not necessarily cause upstream failures.&lt;/li>
&lt;li>Consumers can process and scale independently.&lt;/li>
&lt;li>Fully managed AWS service with minimal operational overhead.&lt;/li>
&lt;li>Natural fit with Deal Engine&amp;rsquo;s existing AWS infrastructure.&lt;/li>
&lt;/ul>
&lt;h3 id="negative">Negative
&lt;/h3>&lt;ul>
&lt;li>Asynchronous workflows introduce eventual consistency.&lt;/li>
&lt;li>SQS provides at-least-once delivery, so consumers must handle duplicates.&lt;/li>
&lt;li>Distributed asynchronous workflows require stronger observability and correlation.&lt;/li>
&lt;li>Engineers must account for queue-specific concepts such as visibility timeout and message retention.&lt;/li>
&lt;/ul>
&lt;h2 id="related-decisions">Related Decisions
&lt;/h2>&lt;ul>
&lt;li>ADR-0024: Idempotency Strategy for SQS Consumers&lt;/li>
&lt;li>ADR-0025: Transactional Outbox for Reliable Message Publication&lt;/li>
&lt;li>ADR-0026: Dead-Letter Queue and Retry Strategy&lt;/li>
&lt;li>ADR-0027: Use SNS for Event Fan-Out&lt;/li>
&lt;/ul></description></item><item><title/><link>https://corebaseit.com/deal-engine/adr-0003/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/deal-engine/adr-0003/</guid><description/></item><item><title/><link>https://corebaseit.com/deal-engine/adr-0004/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/deal-engine/adr-0004/</guid><description>&lt;h1 id="adr-0001-use-amazon-sns--sqs-for-inter-service-communication-claude">ADR-0001: Use Amazon SNS + SQS for Inter-Service Communication (Claude)
&lt;/h1>&lt;h2 id="status">Status
&lt;/h2>&lt;p>Accepted&lt;/p>
&lt;h2 id="date">Date
&lt;/h2>&lt;p>2025-11-15&lt;/p>
&lt;h2 id="context">Context
&lt;/h2>&lt;p>Our platform has grown from 3 microservices to 12 over the past year.
Services currently communicate synchronously via HTTP REST calls. This
creates several problems:&lt;/p>
&lt;ul>
&lt;li>Cascade failures: when the inventory service is down, the order
service can&amp;rsquo;t process orders, even though inventory checks could
be eventual.&lt;/li>
&lt;li>Tight coupling: services need to know each other&amp;rsquo;s API contracts
and endpoints.&lt;/li>
&lt;li>Performance bottlenecks: some operations trigger chains of 4-5
synchronous calls, adding latency.&lt;/li>
&lt;/ul>
&lt;p>We process approximately 100K events per day. We expect this to grow
to 500K within 12 months. That is roughly 1-6 messages per second on
average, with bursts well inside the limits of any managed broker.&lt;/p>
&lt;p>Deal Engine already runs almost entirely on AWS: compute, storage,
networking, IAM, and observability are all AWS-native, and our
infrastructure is defined in Terraform against AWS providers. The team
has 8 backend engineers. All 8 work with AWS services daily; 2 have
prior Kafka experience.&lt;/p>
&lt;h2 id="decision">Decision
&lt;/h2>&lt;p>We will adopt &lt;strong>Amazon SNS + SQS&lt;/strong> as the primary mechanism for
asynchronous inter-service communication, using the standard fan-out
topology:&lt;/p>
&lt;ul>
&lt;li>Producers publish domain events to an &lt;strong>SNS topic&lt;/strong> per event family
(e.g. &lt;code>orders&lt;/code>, &lt;code>inventory&lt;/code>, &lt;code>payments&lt;/code>).&lt;/li>
&lt;li>Each consuming service owns a &lt;strong>dedicated SQS queue&lt;/strong> subscribed to
the relevant topic(s), with a filter policy so it only receives the
message types it cares about.&lt;/li>
&lt;li>Every queue gets a &lt;strong>dead-letter queue&lt;/strong> with a redrive policy
(default &lt;code>maxReceiveCount: 5&lt;/code>).&lt;/li>
&lt;/ul>
&lt;p>Where ordering matters — financial events in particular — we will use
&lt;strong>FIFO topics and FIFO queues&lt;/strong>, with the message group ID set to the
entity being mutated (e.g. &lt;code>deal_id&lt;/code>, &lt;code>account_id&lt;/code>). This gives
ordering per entity plus deduplication, while still allowing parallel
processing across entities.&lt;/p>
&lt;p>Synchronous HTTP remains for request/response patterns where the
caller needs an immediate result (e.g. authentication checks).&lt;/p>
&lt;p>Topics, queues, subscriptions, filter policies, and DLQs are all
defined in Terraform alongside the rest of our infrastructure. Access
is granted through IAM roles, not shared credentials.&lt;/p>
&lt;h2 id="alternatives-considered">Alternatives Considered
&lt;/h2>&lt;h3 id="apache-kafka-confluent-cloud-or-msk">Apache Kafka (Confluent Cloud or MSK)
&lt;/h3>&lt;ul>
&lt;li>Pros: Log-based retention makes event replay and rewind trivial.
Strong ecosystem for stream processing (Kafka Streams, ksqlDB).
Partition-level ordering with high throughput. Vendor-neutral in
principle.&lt;/li>
&lt;li>Cons: Substantial operational and conceptual surface area — brokers
or a managed vendor, schema registry, consumer groups, partition
and rebalance tuning, offset management. Only 2 of 8 engineers have
worked with it. Introduces a second infrastructure vendor with its
own billing, networking (PrivateLink/VPC peering), IAM model, and
Terraform provider, none of which composes with our existing AWS
setup. Baseline cost is a fixed monthly cluster charge that dwarfs
our actual usage at 100-500K events/day.&lt;/li>
&lt;li>Why rejected: We would be paying the full price of Kafka&amp;rsquo;s
complexity to serve a workload two to three orders of magnitude
below where that complexity starts to pay for itself. &amp;ldquo;Vendor
neutrality&amp;rdquo; is largely theoretical for us — we are already on AWS
for everything else, so adding Confluent does not reduce lock-in,
it adds a second lock-in.&lt;/li>
&lt;/ul>
&lt;h3 id="rabbitmq">RabbitMQ
&lt;/h3>&lt;ul>
&lt;li>Pros: Simpler than Kafka, flexible routing (topic, direct, fanout
exchanges), mature and well understood.&lt;/li>
&lt;li>Cons: We would either self-host (brokers, clustering, quorum
queues, upgrades, on-call) or run Amazon MQ, which costs more than
SNS+SQS for the same job. Weak replay. No advantage over SQS for
our access patterns.&lt;/li>
&lt;li>Why rejected: All of the operational burden of running a broker,
none of the benefits over a fully managed AWS-native option.&lt;/li>
&lt;/ul>
&lt;h3 id="keep-synchronous-http-with-circuit-breakers">Keep Synchronous HTTP (with circuit breakers)
&lt;/h3>&lt;ul>
&lt;li>Pros: No new infrastructure. Team already familiar. Circuit
breakers address cascade failures.&lt;/li>
&lt;li>Cons: Doesn&amp;rsquo;t solve tight coupling. Latency still accumulates
across call chains. Circuit breakers are a band-aid, not a
solution to the fundamental coupling problem.&lt;/li>
&lt;li>Why rejected: Addresses symptoms, not root cause.&lt;/li>
&lt;/ul>
&lt;h3 id="eventbridge">EventBridge
&lt;/h3>&lt;ul>
&lt;li>Pros: Also AWS-native. Richer content-based routing than SNS filter
policies, schema registry, built-in archive and replay, and native
third-party SaaS integrations.&lt;/li>
&lt;li>Cons: Higher per-event cost, no FIFO ordering guarantees, and
at-least-once delivery with weaker throughput ceilings for
high-fan-in workloads.&lt;/li>
&lt;li>Why rejected (for now): Not rejected outright. SNS+SQS covers our
service-to-service backbone more cheaply and with the ordering
guarantees we need. We expect to use EventBridge alongside it for
cross-account, SaaS, and scheduled/rule-driven integrations, and
potentially for the event archive (see Risks).&lt;/li>
&lt;/ul>
&lt;h2 id="consequences">Consequences
&lt;/h2>&lt;h3 id="positive">Positive
&lt;/h3>&lt;ul>
&lt;li>Services become decoupled: producers publish to a topic and don&amp;rsquo;t
need to know who consumes it. New consumers are added by creating a
queue and a subscription, with no producer change.&lt;/li>
&lt;li>Cascade failures eliminated for async workflows. A down consumer
means a growing queue, not a failed order.&lt;/li>
&lt;li>No infrastructure to operate: no brokers, no clusters, no patching,
no capacity planning, no 3am rebalance debugging.&lt;/li>
&lt;li>Native fit with what we already run: IAM for authz, CloudWatch for
metrics and alarms, X-Ray for tracing, Terraform for provisioning,
Lambda and ECS for consumers with SQS-driven autoscaling.&lt;/li>
&lt;li>Dead-letter queues and redrive are built in. Poison messages are
isolated and replayable without custom tooling.&lt;/li>
&lt;li>Costs scale to zero and stay small. At 500K events/day (~15M/month)
fanned out to a handful of consumers, this is on the order of tens
of dollars a month, versus a fixed several-hundred-dollar floor for
a managed Kafka cluster. (Approximate — SQS ~$0.40/M requests
standard, ~$0.50/M FIFO; SNS ~$0.50/M publishes with SQS deliveries
free. Verify current pricing for our region before budgeting.)&lt;/li>
&lt;li>Every engineer on the team can already reason about it.&lt;/li>
&lt;/ul>
&lt;h3 id="negative">Negative
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>No event log, no free replay.&lt;/strong> This is the real trade-off
against Kafka. SQS is a queue, not a log: once a message is
consumed and deleted it&amp;rsquo;s gone, and maximum retention is 14 days.
Replaying last quarter&amp;rsquo;s events is not something the transport
gives us. See Risks for mitigation.&lt;/li>
&lt;li>&lt;strong>No built-in stream processing.&lt;/strong> There is no equivalent of Kafka
Streams. Aggregations, joins, and windowing must be done in
consumer code, or by moving that specific workload to Kinesis Data
Streams / Firehose if one ever justifies it.&lt;/li>
&lt;li>&lt;strong>Full event-sourcing patterns are off the table&lt;/strong> with this
transport alone. If a future service genuinely needs an immutable
ordered log as its source of truth, it should use DynamoDB Streams,
Kinesis, or a dedicated event store — and that will need its own
ADR rather than being bolted onto SQS.&lt;/li>
&lt;li>&lt;strong>Deeper AWS lock-in.&lt;/strong> We accept this consciously. Mitigation: keep
a thin internal publish/subscribe interface in our shared library so
that SNS/SQS SDK calls are not scattered through domain code.&lt;/li>
&lt;li>&lt;strong>256 KB message size limit.&lt;/strong> Large payloads need the S3 extended
client (claim check pattern) or, better, slimmer events that carry
IDs rather than full entity snapshots.&lt;/li>
&lt;li>Eventual consistency replaces strong consistency for async flows.
Some workflows need redesign.&lt;/li>
&lt;li>Delivery is at-least-once on standard queues, so consumers must be
idempotent. FIFO queues offer deduplication within a 5-minute
window, which helps but is not a substitute for idempotent handlers.&lt;/li>
&lt;li>Debugging distributed async flows is harder than tracing
synchronous HTTP calls. We&amp;rsquo;ll need distributed tracing
(see ADR-0024).&lt;/li>
&lt;/ul>
&lt;h3 id="risks">Risks
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>Loss of audit/debug replay.&lt;/strong> Mitigation: subscribe an archival
consumer to each topic that writes raw events to S3 (via Firehose
or a small Lambda), partitioned by date. This gives us durable,
queryable history through Athena at negligible cost, and a path to
re-publish if needed. This should be set up as part of the initial
rollout, not deferred.&lt;/li>
&lt;li>&lt;strong>FIFO throughput ceilings.&lt;/strong> Default FIFO queues handle 300
messages/sec (3,000 with batching); high-throughput mode raises
this substantially. Our projected peak is far below the default,
but message group ID choice matters — grouping too coarsely (e.g.
one group for all events) serialises everything. Group by entity.&lt;/li>
&lt;li>&lt;strong>SNS FIFO topics can only deliver to SQS FIFO queues.&lt;/strong> Any
consumer of an ordered topic must use a FIFO queue; Lambda and HTTP
subscriptions won&amp;rsquo;t work directly on those topics.&lt;/li>
&lt;li>&lt;strong>Filter policy sprawl.&lt;/strong> As topics grow, subscription filters can
become an undocumented routing layer. Keep them in Terraform, keep
them simple, and prefer more topics over more elaborate filters.&lt;/li>
&lt;li>&lt;strong>Schema evolution across services still requires discipline&lt;/strong> — we
lose Confluent&amp;rsquo;s schema registry. Plan: versioned event contracts
in a shared package, additive-only changes, consumers ignore
unknown fields. Consider EventBridge Schema Registry if this
becomes painful.&lt;/li>
&lt;/ul>
&lt;h2 id="related-decisions">Related Decisions
&lt;/h2>&lt;ul>
&lt;li>ADR-0024: Adopt OpenTelemetry for Distributed Tracing&lt;/li>
&lt;li>ADR-0018: Service Communication Contracts (superseded by this ADR)&lt;/li>
&lt;/ul></description></item><item><title/><link>https://corebaseit.com/deal-engine/adr-003-choice-of-message-broker/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/deal-engine/adr-003-choice-of-message-broker/</guid><description>&lt;h1 id="adr-003-choice-of-message-broker-for-asynchronous-job-orchestration">ADR 003: Choice of Message Broker for Asynchronous Job Orchestration
&lt;/h1>&lt;p>&lt;strong>Google Cloud Tasks vs. Google Cloud Pub/Sub&lt;/strong>&lt;/p>
&lt;p>This record documents the architectural reasoning for transitioning the Deal Engine platform away from its current database-polling model toward a modern, event-driven task queue. It summarises the comparison between the two candidate Google Cloud messaging technologies and the rationale for the selected option.&lt;/p>
&lt;hr>
&lt;h2 id="1-summary">1. Summary
&lt;/h2>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Field&lt;/th>
&lt;th>Value&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Status&lt;/strong>&lt;/td>
&lt;td>Proposed&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Scope&lt;/strong>&lt;/td>
&lt;td>Deal Engine — asynchronous job orchestration&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Decision&lt;/strong>&lt;/td>
&lt;td>Adopt Google Cloud Tasks; deprecate NoSQL polling&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Affected Services&lt;/strong>&lt;/td>
&lt;td>Radar, Papi Regional Worker, PAPI Scheduler, NoSQL task store&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="2-context-the-database-as-a-queue-bottleneck">2. Context: The Database-as-a-Queue Bottleneck
&lt;/h2>&lt;p>Currently, the regional communication services (Radar and Papi) actively poll the NoSQL database to claim and execute ticket processing tasks. [6, 8, 12] This database-as-a-queue model suffers from several distributed systems pain points:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Severe concurrency risks:&lt;/strong> Multiple workers can claim the same ticket and trigger double-refunds. [4, 6]&lt;/li>
&lt;li>&lt;strong>Continuous read overhead:&lt;/strong> High-frequency polling imposes constant load on the NoSQL instance irrespective of task volume. [6]&lt;/li>
&lt;li>&lt;strong>No native backpressure:&lt;/strong> There is no queue-level mechanism to protect fragile third-party airline APIs from traffic spikes. [12]&lt;/li>
&lt;/ul>
&lt;h2 id="3-options-evaluated">3. Options Evaluated
&lt;/h2>&lt;p>The architectural evaluation compared two primary GCP messaging technologies to resolve these bottlenecks:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Evaluation Criteria&lt;/th>
&lt;th>Option 1: Google Cloud Tasks (Recommended)&lt;/th>
&lt;th>Option 2: Google Cloud Pub/Sub&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Messaging Model&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Target-Specific Task Queue (Message-to-Endpoint)&lt;/strong> — the broker manages queues and actively pushes tasks to specific worker HTTP endpoints.&lt;/td>
&lt;td>&lt;strong>Publish/Subscribe (Message-to-Subscription)&lt;/strong> — high-throughput topic-based broadcasting where multiple subscribers pull or receive pushes.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Rate-Limiting &amp;amp; Backpressure&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Excellent (Built-in)&lt;/strong> — allows native configuration of maximum dispatch rates (tasks/second) and concurrent dispatches per queue. [5]&lt;/td>
&lt;td>&lt;strong>Poor (Client-Side)&lt;/strong> — throttling and flow control must be manually programmed and managed at the individual subscriber/worker level. [5]&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Deduplication&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Native &amp;amp; Long-Lived&lt;/strong> — automatically rejects duplicate tasks matching a custom Task Name within a 20-hour window. [5]&lt;/td>
&lt;td>&lt;strong>No Native Deduplication&lt;/strong> — requires manual implementation of lock tables or state validation inside the worker code or database layer. [5]&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Workload Alignment&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Perfect Fit&lt;/strong> — ticket processing is naturally a point-to-point task (Scheduler → Worker) rather than a multi-system fan-out. [5]&lt;/td>
&lt;td>&lt;strong>Overkill&lt;/strong> — high-throughput, horizontal event streaming is designed for massive event fan-outs rather than single-worker task execution.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Infrastructure Overhead&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Zero&lt;/strong> — already a listed, pre-existing asset in Deal Engine&amp;rsquo;s core GCP infrastructure inventory. [10]&lt;/td>
&lt;td>&lt;strong>Medium&lt;/strong> — requires provisioning new Pub/Sub topics, subscriptions, and access permissions.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="4-decision--justification">4. Decision &amp;amp; Justification
&lt;/h2>&lt;blockquote>
&lt;p>We select &lt;strong>Google Cloud Tasks&lt;/strong> to replace the NoSQL polling mechanism.&lt;/p>&lt;/blockquote>
&lt;p>The driving factor is that rate-limiting and strict deduplication are paramount when dealing with fragile GDS partners and high-stakes financial operations. [5, 12] Cloud Tasks allows us to:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Enforce hard rate limits&lt;/strong> at the queue level, protecting third-party airline endpoints from being overwhelmed during traffic spikes. [5]&lt;/li>
&lt;li>&lt;strong>Utilize native deduplication&lt;/strong> by naming the task using our deterministic &lt;code>idempotency_key&lt;/code>. If a duplicate execution is triggered, Cloud Tasks drops it at the broker level before it can ever reach Radar, entirely preventing accidental duplicate ticket transactions. [5]&lt;/li>
&lt;li>&lt;strong>Minimize infrastructure procurement delays&lt;/strong> by leveraging a resource already approved and listed in our cloud stack. [10]&lt;/li>
&lt;/ol>
&lt;h2 id="5-consequences">5. Consequences
&lt;/h2>&lt;ul>
&lt;li>&lt;strong>Positive:&lt;/strong> The NoSQL database is immediately freed from polling CPU and IOPS overhead [6], backpressure is enforced at the queue level, and duplicate executions are programmatically prevented. [5]&lt;/li>
&lt;li>&lt;strong>Neutral:&lt;/strong> Regional workers (Radar and Papi) must be refactored to act as HTTP target endpoints, secured via private VPC networks and IAM authentication. [8]&lt;/li>
&lt;/ul>
&lt;h2 id="6-migration-strategy">6. Migration Strategy
&lt;/h2>&lt;p>A four-phase migration path will be executed to ensure zero downtime for active transactional traffic:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Phase&lt;/th>
&lt;th>Stage&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Phase 1&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Shadowing&lt;/strong>&lt;/td>
&lt;td>Tasks are dispatched to Cloud Tasks alongside the existing polling flow, without executing the final GDS action, to validate connectivity and payload handling.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Phase 2&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Canary Routing&lt;/strong>&lt;/td>
&lt;td>A small share of live transactional traffic is routed through Cloud Tasks while the legacy polling mechanism remains available as a fallback.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Phase 3&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Scale-Up&lt;/strong>&lt;/td>
&lt;td>Traffic through Cloud Tasks is increased incrementally while dispatch stability, retry rates, and success rates are monitored.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Phase 4&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Polling Deprecation&lt;/strong>&lt;/td>
&lt;td>Once stability is confirmed, the legacy polling logic is decommissioned and the associated NoSQL schemas are retired.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table></description></item><item><title/><link>https://corebaseit.com/deal-engine/adr-003-message-broker-selection/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/deal-engine/adr-003-message-broker-selection/</guid><description>&lt;h1 id="adr-003-message-broker-selection-for-asynchronous-job-orchestration">ADR 003: Message Broker Selection for Asynchronous Job Orchestration
&lt;/h1>&lt;p>&lt;strong>Replacing NoSQL Polling&lt;/strong>&lt;/p>
&lt;p>The current reliance on NoSQL polling for core transactional workflows has reached its scaling limit, necessitating a shift to a push-based orchestration model. This ADR outlines the transition to a managed message broker to support Deal Engine&amp;rsquo;s multi-cloud evolution, ensuring regional reliability while safely decomposing the Aleron monolith.&lt;/p>
&lt;hr>
&lt;h2 id="1-status-and-meta-data">1. Status and Meta-Data
&lt;/h2>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Field&lt;/th>
&lt;th>Value&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Status&lt;/strong>&lt;/td>
&lt;td>Proposed&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Date&lt;/strong>&lt;/td>
&lt;td>October 24, 2023&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Deciders&lt;/strong>&lt;/td>
&lt;td>Principal Engineering Team&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Consulted&lt;/strong>&lt;/td>
&lt;td>Infrastructure, DevOps, and Backend Engineering (PAPI and Radar teams)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="2-context-and-problem-statement">2. Context and Problem Statement
&lt;/h2>&lt;p>The Deal Engine architecture currently utilizes a &amp;ldquo;Database-as-a-Queue&amp;rdquo; anti-pattern. In this model, the Aleron core and the PAPI Coordinator (the logic engine) write task states to a NoSQL database. Regional workers—specifically Radar and the Papi Regional Worker—continuously poll this database to identify pending tasks. Once a task is retrieved, the workers execute the action against external Global Distribution Systems (GDS) and airline APIs via the Twingate Client.&lt;/p>
&lt;h3 id="operational-pain-points">Operational Pain Points
&lt;/h3>&lt;p>This &amp;ldquo;pull-based&amp;rdquo; model introduces significant systemic risks:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Concurrency and Race Conditions:&lt;/strong> The lack of atomic message visibility leads to multiple workers claiming the same task. In the travel domain, this results in duplicate ticket transactions and double-refunds.&lt;/li>
&lt;li>&lt;strong>Resource Inefficiency:&lt;/strong> High-frequency polling against the NoSQL instance creates constant, unnecessary CPU and I/O load, regardless of actual task volume.&lt;/li>
&lt;li>&lt;strong>Backpressure Deficits:&lt;/strong> There is no native mechanism to throttle outgoing requests. This leaves external GDS endpoints vulnerable to traffic spikes, risking IP blocking and rate-limit penalties.&lt;/li>
&lt;li>&lt;strong>Non-Deterministic Latency for Rudder:&lt;/strong> The polling delay negatively impacts Rudder (the Tax Calculator), creating inconsistent timing for critical tax and fee calculations that must precede ticket finalization.&lt;/li>
&lt;li>&lt;strong>Fragile Error Recovery:&lt;/strong> Retries and crash recovery are managed manually via database state. If a worker fails mid-execution, tasks often hang in a &amp;ldquo;Processing&amp;rdquo; state without native visibility timeouts.&lt;/li>
&lt;/ul>
&lt;h2 id="3-decision-drivers">3. Decision Drivers
&lt;/h2>&lt;p>The selection of a replacement broker is governed by the specialized constraints of the travel industry rather than raw throughput. Our primary requirement is the protection of fragile external endpoints.&lt;/p>
&lt;h3 id="key-differentiators">Key Differentiators
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>Granular Rate Limiting:&lt;/strong> We must strictly enforce Transactions Per Second (TPS) limits at the queue level to honor GDS constraints.&lt;/li>
&lt;li>&lt;strong>Guaranteed Idempotency:&lt;/strong> The system must support deterministic keys to prevent duplicate execution of any ticket action.&lt;/li>
&lt;li>&lt;strong>Scheduled Execution:&lt;/strong> Many Deal Engine jobs require delayed execution (e.g., &amp;ldquo;process refund in 2 hours&amp;rdquo;), a feature currently managed poorly by database timestamps.&lt;/li>
&lt;li>&lt;strong>Operational Simplicity:&lt;/strong> We will prioritize existing Google Cloud Platform (GCP) assets to minimize security review friction and leverage our established GKE and Cloud Run footprints.&lt;/li>
&lt;/ul>
&lt;h2 id="4-options-evaluated">4. Options Evaluated
&lt;/h2>&lt;h3 id="option-1-google-cloud-tasks-the-targeted-queue">Option 1: Google Cloud Tasks (The Targeted Queue)
&lt;/h3>&lt;p>Cloud Tasks is a distributed queue service designed for &amp;ldquo;message-to-endpoint&amp;rdquo; push communication.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Push Model:&lt;/strong> Cloud Tasks explicitly targets HTTP endpoints, allowing it to trigger workers residing on our existing Google Kubernetes Engine (GKE) and Cloud Run clusters.&lt;/li>
&lt;li>&lt;strong>Native Scheduling:&lt;/strong> It supports a &lt;code>schedule_time&lt;/code> parameter, allowing the Scheduler to offload future-dated jobs directly to the broker.&lt;/li>
&lt;li>&lt;strong>Deduplication:&lt;/strong> It provides deterministic deduplication via the &amp;ldquo;Task Name&amp;rdquo; feature.&lt;/li>
&lt;li>&lt;strong>Rate Control:&lt;/strong> Offers native &lt;code>max_dispatch_rate&lt;/code> and &lt;code>max_concurrent_dispatches&lt;/code> to protect downstream APIs and the Twingate egress point.&lt;/li>
&lt;/ul>
&lt;h3 id="option-2-google-cloud-pubsub-the-event-driven-broker">Option 2: Google Cloud Pub/Sub (The Event-Driven Broker)
&lt;/h3>&lt;p>Pub/Sub is a high-scale asynchronous messaging service designed for fan-out patterns.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Limitations:&lt;/strong> Pub/Sub lacks native, broker-side rate limiting and does not support scheduled message delivery (delayed messages).&lt;/li>
&lt;li>&lt;strong>Delivery Model:&lt;/strong> It follows an &amp;ldquo;at-least-once&amp;rdquo; delivery model without the deterministic deduplication required to prevent double-refunds.&lt;/li>
&lt;li>&lt;strong>Contextual Fit:&lt;/strong> While excellent for high-throughput analytics ingestion, it is ill-suited for the point-to-point, task-oriented coordination required between the PAPI Coordinator and regional workers.&lt;/li>
&lt;/ul>
&lt;h2 id="5-decision-outcome--justification">5. Decision Outcome &amp;amp; Justification
&lt;/h2>&lt;blockquote>
&lt;p>The engineering team will implement &lt;strong>Google Cloud Tasks&lt;/strong> as the primary orchestration engine for core transactional operations.&lt;/p>&lt;/blockquote>
&lt;h3 id="strategic-alignment">Strategic Alignment
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>TPS Protection:&lt;/strong> The max-dispatch-rate feature in Cloud Tasks acts as a native &amp;ldquo;Control Tower,&amp;rdquo; ensuring we never exceed GDS TPS limits regardless of internal traffic spikes.&lt;/li>
&lt;li>&lt;strong>Deterministic Idempotency:&lt;/strong> We will implement a standardized naming convention for tasks: &lt;code>[service]-[ticketID]-[transactionID]-[attemptCount]&lt;/code>. Because Cloud Tasks prevents the creation of two tasks with the same name within a queue, this effectively eliminates the &amp;ldquo;double-worker&amp;rdquo; race condition at the infrastructure level.&lt;/li>
&lt;li>&lt;strong>Virtual Control Tower:&lt;/strong> While the legacy Control Tower UI remains for business visibility, Cloud Tasks provides an operational Control Tower via the GCP Console, offering native visibility into queue depth, retry rates, and execution latency.&lt;/li>
&lt;li>&lt;strong>Infrastructure Synergy:&lt;/strong> Cloud Tasks is already an approved asset within the Deal Engine Primary Infrastructure list. It integrates seamlessly with our current GKE/Cloud Run execution environments without requiring new vendor procurement.&lt;/li>
&lt;/ul>
&lt;h2 id="6-consequences-and-impact-analysis">6. Consequences and Impact Analysis
&lt;/h2>&lt;h3 id="positive-impacts">Positive Impacts
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>Reduced Database Pressure:&lt;/strong> Removing polling will significantly lower NoSQL I/O, allowing that resource to scale more effectively for state persistence.&lt;/li>
&lt;li>&lt;strong>Stabilized Rudder Calculations:&lt;/strong> Transitioning to a push model ensures that tax calculations are triggered immediately upon task creation, reducing non-deterministic wait times.&lt;/li>
&lt;li>&lt;strong>Operational Reliability:&lt;/strong> Native dead-letter queues and configurable retry backoffs replace fragile, manual database state management.&lt;/li>
&lt;/ul>
&lt;h3 id="negative--neutral-impacts">Negative / Neutral Impacts
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>Worker Refactoring:&lt;/strong> The Radar and Papi workers must be updated to serve as secured HTTP targets. This requires configuring Identity-Aware Proxy (IAP) or service-to-service OIDC authentication.&lt;/li>
&lt;li>&lt;strong>Retention Management:&lt;/strong> Cloud Tasks has a 31-day task retention limit. Long-term auditing of task outcomes must still be persisted to Cloud SQL.&lt;/li>
&lt;/ul>
&lt;h2 id="7-migration-roadmap">7. Migration Roadmap
&lt;/h2>&lt;p>To adhere to our Zero-Interruption mandate, the transition will follow a four-phase approach:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Phase&lt;/th>
&lt;th>Activity&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Phase 1&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Shadowing / Dry-run&lt;/strong>&lt;/td>
&lt;td>The PAPI Coordinator will begin dispatching tasks to Cloud Tasks in a &amp;ldquo;shadow&amp;rdquo; mode. Workers will receive the payload but will not execute the final GDS action, allowing us to validate regional connectivity through the Twingate Client.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Phase 2&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Canary Hybrid Routing&lt;/strong>&lt;/td>
&lt;td>We will divert a small percentage (e.g., 5%) of transactional traffic through Cloud Tasks. The remaining 95% will continue via the legacy NoSQL polling mechanism as a fallback.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Phase 3&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Scale-up&lt;/strong>&lt;/td>
&lt;td>We will gradually increase the traffic percentage while monitoring the GCP Console and internal observability tools for dispatch stability and success rates.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Phase 4&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Polling Deprecation&lt;/strong>&lt;/td>
&lt;td>Once 100% stability is achieved over a full business cycle, we will decommission the legacy polling logic in the PAPI Communication Service and clean up the associated NoSQL schemas.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table></description></item><item><title/><link>https://corebaseit.com/deal-engine/adr-completed/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/deal-engine/adr-completed/</guid><description>&lt;h1 id="adr-0023-use-apache-kafka-for-inter-service-communication">ADR-0023: Use Apache Kafka for Inter-Service Communication
&lt;/h1>&lt;h2 id="status">Status
&lt;/h2>&lt;p>Accepted&lt;/p>
&lt;h2 id="date">Date
&lt;/h2>&lt;p>2025-11-15&lt;/p>
&lt;h2 id="context">Context
&lt;/h2>&lt;p>Our platform has grown from 3 microservices to 12 over the past year.
Services currently communicate synchronously via HTTP REST calls. This
creates several problems:&lt;/p>
&lt;ul>
&lt;li>Cascade failures: when the inventory service is down, the order
service can&amp;rsquo;t process orders, even though inventory checks could
be eventual.&lt;/li>
&lt;li>Tight coupling: services need to know each other&amp;rsquo;s API contracts
and endpoints.&lt;/li>
&lt;li>Performance bottlenecks: some operations trigger chains of 4-5
synchronous calls, adding latency.&lt;/li>
&lt;/ul>
&lt;p>We process approximately 100K events per day. We expect this to grow
to 500K within 12 months. The team has 8 backend engineers, 2 of whom
have prior Kafka experience.&lt;/p>
&lt;h2 id="decision">Decision
&lt;/h2>&lt;p>We will adopt Apache Kafka as the primary mechanism for asynchronous
inter-service communication. Synchronous HTTP will remain for
request/response patterns where the caller needs an immediate result
(e.g., authentication checks).&lt;/p>
&lt;p>We will use Confluent Cloud as the managed Kafka provider to minimize
operational overhead.&lt;/p>
&lt;h2 id="alternatives-considered">Alternatives Considered
&lt;/h2>&lt;h3 id="rabbitmq">RabbitMQ
&lt;/h3>&lt;ul>
&lt;li>Pros: Simpler to operate, lower learning curve, supports multiple
messaging patterns (pub/sub, point-to-point, routing).&lt;/li>
&lt;li>Cons: Less suitable for event sourcing patterns we plan to adopt.
Weaker replay/rewind capabilities. Community momentum has shifted
toward Kafka for event-driven architectures.&lt;/li>
&lt;li>Why rejected: We anticipate needing event replay for audit and
debugging. Kafka&amp;rsquo;s log-based architecture is better suited.&lt;/li>
&lt;/ul>
&lt;h3 id="aws-sqs--sns">AWS SQS + SNS
&lt;/h3>&lt;ul>
&lt;li>Pros: Fully managed, no infrastructure to maintain, tight AWS
integration.&lt;/li>
&lt;li>Cons: Vendor lock-in to AWS. Limited message ordering guarantees.
No built-in stream processing (would need Kinesis or Lambda).
Higher per-message cost at our projected volume.&lt;/li>
&lt;li>Why rejected: We want to avoid deepening AWS lock-in, and we need
ordered message delivery for financial events.&lt;/li>
&lt;/ul>
&lt;h3 id="keep-synchronous-http-with-circuit-breakers">Keep Synchronous HTTP (with circuit breakers)
&lt;/h3>&lt;ul>
&lt;li>Pros: No new infrastructure. Team already familiar. Circuit
breakers address cascade failures.&lt;/li>
&lt;li>Cons: Doesn&amp;rsquo;t solve tight coupling. Latency still accumulates
across call chains. Circuit breakers are a band-aid, not a
solution to the fundamental coupling problem.&lt;/li>
&lt;li>Why rejected: Addresses symptoms, not root cause.&lt;/li>
&lt;/ul>
&lt;h2 id="consequences">Consequences
&lt;/h2>&lt;h3 id="positive">Positive
&lt;/h3>&lt;ul>
&lt;li>Services become decoupled: producers don&amp;rsquo;t need to know consumers.&lt;/li>
&lt;li>Cascade failures eliminated for async workflows.&lt;/li>
&lt;li>Event replay enables powerful debugging and audit capabilities.&lt;/li>
&lt;li>Foundation for event sourcing patterns in future services.&lt;/li>
&lt;/ul>
&lt;h3 id="negative">Negative
&lt;/h3>&lt;ul>
&lt;li>Operational complexity increases (Kafka cluster, schema registry,
consumer group management). Mitigated by using Confluent Cloud.&lt;/li>
&lt;li>Team needs training on Kafka concepts and patterns.&lt;/li>
&lt;li>Eventual consistency replaces strong consistency for async flows.
Some workflows need redesign.&lt;/li>
&lt;li>Debugging distributed async flows is harder than tracing synchronous
HTTP calls. We&amp;rsquo;ll need distributed tracing (see ADR-0024).&lt;/li>
&lt;/ul>
&lt;h3 id="risks">Risks
&lt;/h3>&lt;ul>
&lt;li>If message volume exceeds Confluent Cloud pricing tiers, costs
could increase significantly. Monitor and set alerts.&lt;/li>
&lt;li>Schema evolution across services requires discipline. Plan to
adopt Avro with schema registry.&lt;/li>
&lt;/ul>
&lt;h2 id="related-decisions">Related Decisions
&lt;/h2>&lt;ul>
&lt;li>ADR-0024: Adopt OpenTelemetry for Distributed Tracing&lt;/li>
&lt;li>ADR-0018: Service Communication Contracts (superseded by this ADR)&lt;/li>
&lt;/ul></description></item><item><title/><link>https://corebaseit.com/deal-engine/adr/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/deal-engine/adr/</guid><description>&lt;h1 id="adr-nnnn-short-title-describing-the-decision">ADR-NNNN: [Short Title Describing the Decision]
&lt;/h1>&lt;h2 id="status">Status
&lt;/h2>&lt;p>[Proposed | Accepted | Deprecated | Superseded by ADR-XXXX]&lt;/p>
&lt;h2 id="date">Date
&lt;/h2>&lt;p>[YYYY-MM-DD]&lt;/p>
&lt;h2 id="context">Context
&lt;/h2>&lt;p>[Describe the situation. What problem are we solving? What constraints
exist? What forces are at play? Be specific &amp;ndash; include numbers, deadlines,
team capabilities, and technical requirements where relevant.]&lt;/p>
&lt;h2 id="decision">Decision
&lt;/h2>&lt;p>[State the decision clearly and concisely. Use active voice:
&amp;ldquo;We will use X&amp;rdquo; not &amp;ldquo;X should be considered.&amp;rdquo;]&lt;/p>
&lt;h2 id="alternatives-considered">Alternatives Considered
&lt;/h2>&lt;h3 id="alternative-1">[Alternative 1]
&lt;/h3>&lt;ul>
&lt;li>Pros: &amp;hellip;&lt;/li>
&lt;li>Cons: &amp;hellip;&lt;/li>
&lt;li>Why rejected: &amp;hellip;&lt;/li>
&lt;/ul>
&lt;h3 id="alternative-2">[Alternative 2]
&lt;/h3>&lt;ul>
&lt;li>Pros: &amp;hellip;&lt;/li>
&lt;li>Cons: &amp;hellip;&lt;/li>
&lt;li>Why rejected: &amp;hellip;&lt;/li>
&lt;/ul>
&lt;h2 id="consequences">Consequences
&lt;/h2>&lt;h3 id="positive">Positive
&lt;/h3>&lt;ul>
&lt;li>[What becomes easier or better]&lt;/li>
&lt;/ul>
&lt;h3 id="negative">Negative
&lt;/h3>&lt;ul>
&lt;li>[What becomes harder or worse]&lt;/li>
&lt;/ul>
&lt;h3 id="risks">Risks
&lt;/h3>&lt;ul>
&lt;li>[What could go wrong]&lt;/li>
&lt;/ul>
&lt;h2 id="related-decisions">Related Decisions
&lt;/h2>&lt;ul>
&lt;li>[Links to related ADRs]&lt;/li>
&lt;/ul></description></item><item><title/><link>https://corebaseit.com/deal-engine/ard01/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/deal-engine/ard01/</guid><description>&lt;p>For Deal Engine, I’d make the ADR much more specific than “migrate the monolith to microservices.” An ADR should capture &lt;strong>one architectural decision&lt;/strong>.&lt;/p>
&lt;p>Given their domain and the migration context, a strong example would be extracting &lt;strong>Refund Processing&lt;/strong> from the Scala monolith. It naturally lets you discuss migration strategy, GDS/external-provider failures, retries, idempotency, observability, and rollback.&lt;/p>
&lt;p>Here’s a realistic ADR you could use for interview preparation:&lt;/p>
&lt;h1 id="adr-0024-extract-refund-processing-from-the-scala-monolith-into-a-dedicated-service">ADR-0024: Extract Refund Processing from the Scala Monolith into a Dedicated Service
&lt;/h1>&lt;h2 id="status">Status
&lt;/h2>&lt;p>Proposed&lt;/p>
&lt;h2 id="date">Date
&lt;/h2>&lt;p>2026-08-30&lt;/p>
&lt;h2 id="context">Context
&lt;/h2>&lt;p>Deal Engine currently handles refund processing as part of the existing Scala monolith.&lt;/p>
&lt;p>Refund processing has become a good candidate for extraction because it has a relatively clear business boundary and interacts with external systems such as GDS and airline APIs.&lt;/p>
&lt;p>The current implementation creates several challenges:&lt;/p>
&lt;ul>
&lt;li>Refund logic is coupled to the deployment lifecycle of the monolith.&lt;/li>
&lt;li>Failures or latency in external GDS/airline systems can consume resources inside the monolith.&lt;/li>
&lt;li>Retry and recovery logic is difficult to evolve independently.&lt;/li>
&lt;li>Refund operations may take significantly longer than normal request/response operations.&lt;/li>
&lt;li>External systems may successfully process a refund even when Deal Engine does not receive the response.&lt;/li>
&lt;li>Duplicate requests or worker retries must not result in duplicate refunds.&lt;/li>
&lt;li>Observability of the complete refund lifecycle is difficult when processing is embedded inside the larger application.&lt;/li>
&lt;/ul>
&lt;p>Migrating the entire monolith at once would introduce significant technical and operational risk.&lt;/p>
&lt;p>We therefore need a migration approach that allows functionality to be extracted incrementally while the existing platform continues operating.&lt;/p>
&lt;h2 id="decision">Decision
&lt;/h2>&lt;p>We will extract refund processing from the Scala monolith into a dedicated &lt;strong>Refund Service&lt;/strong>.&lt;/p>
&lt;p>The migration will follow an incremental Strangler Fig approach rather than rewriting the existing platform.&lt;/p>
&lt;p>The initial architecture will be:&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>Client / Internal System
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> API Gateway
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Scala Monolith
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ refund request
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Refund Service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── Refund Database
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └── GDS / Airline APIs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>During the first migration phase, the monolith will remain the public entry point for refund requests.&lt;/p>
&lt;p>The monolith will delegate eligible refund operations to the Refund Service.&lt;/p>
&lt;p>The Refund Service will own:&lt;/p>
&lt;ul>
&lt;li>refund orchestration;&lt;/li>
&lt;li>refund state transitions;&lt;/li>
&lt;li>idempotency;&lt;/li>
&lt;li>interaction with GDS and airline APIs;&lt;/li>
&lt;li>retry policies;&lt;/li>
&lt;li>timeout handling;&lt;/li>
&lt;li>reconciliation;&lt;/li>
&lt;li>refund-specific persistence;&lt;/li>
&lt;li>refund-specific observability.&lt;/li>
&lt;/ul>
&lt;p>The service will maintain an explicit refund state machine such as:&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>REQUESTED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PROCESSING
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├───────────────┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼ ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>SUCCEEDED FAILED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └──── UNKNOWN
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> RECONCILIATION
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A refund request must contain an idempotency key or stable refund identifier.&lt;/p>
&lt;p>Repeated processing of the same refund identifier must not create another external refund.&lt;/p>
&lt;p>The first version will preserve synchronous communication between the monolith and Refund Service where an immediate response is available.&lt;/p>
&lt;p>Long-running operations, retries, and reconciliation may later be moved to asynchronous processing through a queue.&lt;/p>
&lt;p>This allows the service boundary to be established before introducing additional messaging infrastructure.&lt;/p>
&lt;h2 id="alternatives-considered">Alternatives Considered
&lt;/h2>&lt;h3 id="rewrite-the-entire-monolith-as-microservices">Rewrite the Entire Monolith as Microservices
&lt;/h3>&lt;p>&lt;strong>Pros&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Clean architecture without transitional components.&lt;/li>
&lt;li>Opportunity to redesign all service boundaries.&lt;/li>
&lt;li>Removes legacy architecture in one migration.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cons&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Very large migration scope.&lt;/li>
&lt;li>Long period before business value is delivered.&lt;/li>
&lt;li>Difficult to validate behavioral equivalence.&lt;/li>
&lt;li>High rollback risk.&lt;/li>
&lt;li>Existing business rules may be poorly documented.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Why rejected&lt;/strong>&lt;/p>
&lt;p>A full rewrite introduces unnecessary risk. Incremental extraction allows individual business capabilities to be migrated and validated independently.&lt;/p>
&lt;hr>
&lt;h3 id="extract-a-simpler-crud-capability-first">Extract a Simpler CRUD Capability First
&lt;/h3>&lt;p>For example, configuration or reference-data management.&lt;/p>
&lt;p>&lt;strong>Pros&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Easier first migration.&lt;/li>
&lt;li>Lower operational risk.&lt;/li>
&lt;li>Useful for validating deployment and infrastructure.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cons&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Does not exercise the difficult architectural problems Deal Engine needs to solve.&lt;/li>
&lt;li>Provides limited evidence for handling concurrency, retries, external-system failures, and long-running workflows.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Why rejected&lt;/strong>&lt;/p>
&lt;p>Refund processing provides a more meaningful vertical slice through the architecture and validates patterns required by future service extractions.&lt;/p>
&lt;hr>
&lt;h3 id="keep-refund-processing-inside-the-monolith">Keep Refund Processing Inside the Monolith
&lt;/h3>&lt;p>&lt;strong>Pros&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>No migration effort.&lt;/li>
&lt;li>No additional service infrastructure.&lt;/li>
&lt;li>Existing transactional model remains unchanged.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cons&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Refund processing remains coupled to the monolith.&lt;/li>
&lt;li>External-system latency continues affecting monolith resources.&lt;/li>
&lt;li>Independent scaling and deployment are impossible.&lt;/li>
&lt;li>Recovery and reconciliation logic remain mixed with unrelated functionality.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Why rejected&lt;/strong>&lt;/p>
&lt;p>This preserves the architectural constraints that the migration is intended to remove.&lt;/p>
&lt;h2 id="consequences">Consequences
&lt;/h2>&lt;h3 id="positive">Positive
&lt;/h3>&lt;ul>
&lt;li>Refund processing can evolve independently from the monolith.&lt;/li>
&lt;li>GDS and airline integration failures are isolated behind a clear service boundary.&lt;/li>
&lt;li>Refund-specific retry and recovery policies become easier to implement.&lt;/li>
&lt;li>Independent deployment becomes possible.&lt;/li>
&lt;li>Independent scaling becomes possible.&lt;/li>
&lt;li>Observability can be built around the complete refund lifecycle.&lt;/li>
&lt;li>The migration pattern can later be reused for other capabilities.&lt;/li>
&lt;/ul>
&lt;p>The architecture can evolve incrementally:&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> MONOLITH
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ┌────────┴────────┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Existing Logic Refund Service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> GDS / Airline
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>followed eventually by:&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 Gateway
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ┌──────────────┼──────────────┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼ ▼ ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Monolith Refund Service Other
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Services
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="negative">Negative
&lt;/h3>&lt;ul>
&lt;li>The platform temporarily operates as a distributed system plus a monolith.&lt;/li>
&lt;li>Network failures must now be considered between the monolith and Refund Service.&lt;/li>
&lt;li>Distributed tracing becomes necessary.&lt;/li>
&lt;li>Data ownership boundaries must be clearly defined.&lt;/li>
&lt;li>Some business transactions can no longer rely on a single database transaction.&lt;/li>
&lt;li>Operational complexity increases.&lt;/li>
&lt;/ul>
&lt;h3 id="risks">Risks
&lt;/h3>&lt;h4 id="duplicate-refund">Duplicate refund
&lt;/h4>&lt;p>A worker may retry after an external refund succeeded but before the response was persisted.&lt;/p>
&lt;p>&lt;strong>Mitigation:&lt;/strong> idempotency keys, persistent refund state, and reconciliation with the external provider.&lt;/p>
&lt;h4 id="unknown-external-state">Unknown external state
&lt;/h4>&lt;p>The GDS may timeout after receiving the request.&lt;/p>
&lt;p>The system must not assume:&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>TIMEOUT == FAILED
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Instead:&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>TIMEOUT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>UNKNOWN
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>QUERY / RECONCILE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>SUCCEEDED | FAILED
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="data-ownership">Data ownership
&lt;/h4>&lt;p>Both the monolith and Refund Service could accidentally become authoritative for refund state.&lt;/p>
&lt;p>&lt;strong>Mitigation:&lt;/strong> once a refund is migrated, the Refund Service becomes the source of truth for refund execution state.&lt;/p>
&lt;h4 id="migration-regressions">Migration regressions
&lt;/h4>&lt;p>Existing refund behavior may contain undocumented business rules.&lt;/p>
&lt;p>&lt;strong>Mitigation:&lt;/strong> migrate incrementally, compare old/new behavior, use feature flags, and initially route only selected refund traffic through the new service.&lt;/p>
&lt;h2 id="rollout-strategy">Rollout Strategy
&lt;/h2>&lt;p>The migration will be gradual:&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>Phase 1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Refund Service deployed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>No production traffic
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Phase 2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Shadow / test traffic
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Compare behaviour
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Phase 3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Small % production traffic
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Monitor
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Phase 4
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Increase traffic
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Phase 5
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>100% refund processing
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Phase 6
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Remove refund logic
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>from monolith
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A feature flag will control whether refund requests are processed by the existing monolith implementation or delegated to the Refund Service.&lt;/p>
&lt;p>Rollback therefore consists of routing new refund requests back to the monolith while investigating the service.&lt;/p>
&lt;h2 id="success-criteria">Success Criteria
&lt;/h2>&lt;p>The extraction will be considered successful when:&lt;/p>
&lt;ul>
&lt;li>refund behavior remains functionally equivalent;&lt;/li>
&lt;li>duplicate refunds are prevented;&lt;/li>
&lt;li>external timeout scenarios are recoverable;&lt;/li>
&lt;li>migrated traffic can be rolled back safely;&lt;/li>
&lt;li>refund processing is independently deployable;&lt;/li>
&lt;li>refund lifecycle metrics and traces are available;&lt;/li>
&lt;li>the monolith no longer owns refund execution logic.&lt;/li>
&lt;/ul>
&lt;h2 id="related-decisions">Related Decisions
&lt;/h2>&lt;ul>
&lt;li>ADR-0025: Refund Service Data Ownership&lt;/li>
&lt;li>ADR-0026: Idempotency Strategy for Refund Processing&lt;/li>
&lt;li>ADR-0027: Asynchronous Refund Processing&lt;/li>
&lt;li>ADR-0028: Distributed Tracing with OpenTelemetry&lt;/li>
&lt;li>ADR-0029: Service-to-GDS Retry and Reconciliation Strategy&lt;/li>
&lt;/ul>
&lt;p>Notice something particularly useful here: &lt;strong>we deliberately did not decide everything in ADR-0024&lt;/strong>.&lt;/p>
&lt;p>For 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-0024
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Extract Refund Service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── ADR-0025
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ Who owns refund data?
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── ADR-0026
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ How do we guarantee idempotency?
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── SPIKE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ Can GDS refund status be queried reliably?
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ ADR-0029
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ Reconciliation strategy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └── SPIKE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SQS vs Kafka vs synchronous worker?
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ↓
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ADR-0027
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Async processing architecture
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That is much closer to how I would expect a &lt;strong>Principal Engineer&lt;/strong> to approach the migration: the ADR isn&amp;rsquo;t “the architecture document.” It records &lt;strong>one consequential decision&lt;/strong>, while Spikes provide evidence for decisions where uncertainty remains.&lt;/p></description></item><item><title/><link>https://corebaseit.com/deal-engine/designdoc-for-adr-0002/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/deal-engine/designdoc-for-adr-0002/</guid><description>&lt;h1 id="adr-0023-use-aws-sqs-and-sns-for-asynchronous-inter-service-communication">ADR-0023: Use AWS SQS and SNS for Asynchronous Inter-Service Communication
&lt;/h1>&lt;h2 id="status">Status
&lt;/h2>&lt;p>Accepted&lt;/p>
&lt;h2 id="date">Date
&lt;/h2>&lt;p>2026-08-31&lt;/p>
&lt;h2 id="context">Context
&lt;/h2>&lt;p>Deal Engine is progressively moving functionality from the existing monolithic platform into independently deployable services.&lt;/p>
&lt;p>As part of this migration, some workflows — such as refund processing, ticket operations, notifications, and external GDS interactions — do not require all processing to complete within a single synchronous request.&lt;/p>
&lt;p>The platform currently relies heavily on synchronous service-to-service communication. This creates several problems:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Cascade failures:&lt;/strong> if a downstream service or external GDS is unavailable, upstream services may also fail or remain blocked waiting for a response.&lt;/li>
&lt;li>&lt;strong>Tight runtime coupling:&lt;/strong> the calling service requires the downstream service to be available at the same time.&lt;/li>
&lt;li>&lt;strong>Latency amplification:&lt;/strong> workflows involving several synchronous calls accumulate latency across the complete request chain.&lt;/li>
&lt;li>&lt;strong>External-system instability:&lt;/strong> GDS and airline APIs may timeout, respond slowly, or become temporarily unavailable.&lt;/li>
&lt;li>&lt;strong>Retry complexity:&lt;/strong> retrying synchronous operations can create duplicate processing unless idempotency is carefully implemented.&lt;/li>
&lt;li>&lt;strong>Independent scaling:&lt;/strong> some workloads, particularly refund and ticket-processing jobs, may need to scale independently from the services producing the work.&lt;/li>
&lt;/ul>
&lt;p>Deal Engine already operates its infrastructure primarily within AWS.&lt;/p>
&lt;p>We therefore need a managed asynchronous messaging mechanism that integrates naturally with the existing AWS environment while introducing minimal additional operational complexity.&lt;/p>
&lt;p>Not all communication should become asynchronous. Operations requiring an immediate response will continue to use synchronous APIs.&lt;/p>
&lt;h2 id="decision">Decision
&lt;/h2>&lt;p>We will use &lt;strong>Amazon SQS as the default mechanism for asynchronous work distribution between services&lt;/strong>.&lt;/p>
&lt;p>Amazon SNS will be used where a single business event needs to be delivered to multiple independent consumers.&lt;/p>
&lt;p>The general distinction will be:&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>SQS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>────
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;#34;Someone needs to process this work.&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Producer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SQS Queue
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Consumer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>SNS + SQS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>─────────
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;#34;Something happened and several systems
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>may independently care about it.&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Producer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SNS Topic
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├──────────────┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼ ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>SQS Queue A SQS Queue B
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼ ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Consumer A Consumer B
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Synchronous HTTP APIs will remain appropriate when the caller requires an immediate result.&lt;/p>
&lt;p>For 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>Authentication request
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ HTTP
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Auth Service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Immediate response
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>while long-running refund processing could use:&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>Refund API
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ create refund
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Refund Service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ persist
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Refund DB
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ enqueue work
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SQS Refund Queue
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Refund Worker
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>GDS / Airline API
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The API may therefore return:&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>202 Accepted
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>refundId = REF-123
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>status = PENDING
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>rather than keeping the original request open while the complete external refund operation is performed.&lt;/p>
&lt;h2 id="message-processing-semantics">Message Processing Semantics
&lt;/h2>&lt;p>SQS Standard queues provide &lt;strong>at-least-once delivery&lt;/strong>.&lt;/p>
&lt;p>Consumers must therefore assume that the same message can be delivered more than once.&lt;/p>
&lt;p>For 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>SQS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ RefundRequested(REF-123)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Worker
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ calls GDS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>GDS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ REFUND SUCCESSFUL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Worker
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 💥 crashes before
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deleting message
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>SQS may later redeliver the message:&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>SQS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ RefundRequested(REF-123)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Worker
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The consumer must not issue a second refund simply because the message was delivered again.&lt;/p>
&lt;p>All consumers processing business-critical operations must therefore be &lt;strong>idempotent&lt;/strong>.&lt;/p>
&lt;p>A stable business identifier such as &lt;code>refundId&lt;/code> will be used to detect previously processed operations.&lt;/p>
&lt;h2 id="retry-strategy">Retry Strategy
&lt;/h2>&lt;p>Transient failures will be retried through SQS.&lt;/p>
&lt;p>The SQS visibility timeout will prevent another worker from immediately processing a message while it is already being handled.&lt;/p>
&lt;p>If processing succeeds, the consumer deletes the message.&lt;/p>
&lt;p>If processing fails, the message becomes visible again after the visibility timeout.&lt;/p>
&lt;p>Conceptually:&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> SQS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Worker
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ┌─────┴─────┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SUCCESS FAILURE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼ ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Delete message Message becomes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> visible again
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Retry
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Retry policies must use bounded retries and backoff where appropriate.&lt;/p>
&lt;p>Messages that repeatedly fail will be moved to a &lt;strong>Dead-Letter Queue (DLQ)&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>SQS Queue
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ retry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Consumer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ retry
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Consumer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ maxReceiveCount exceeded
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Dead-Letter Queue
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>DLQs will be monitored and must have an operational recovery procedure.&lt;/p>
&lt;h2 id="external-system-timeouts">External-System Timeouts
&lt;/h2>&lt;p>A timeout when communicating with a GDS or airline system must not automatically be interpreted as a failed business operation.&lt;/p>
&lt;p>For 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>Refund Worker
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ refund REF-123
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> GDS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ refund succeeds
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> X response lost
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Worker sees:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TIMEOUT
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The actual business state may now be:&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>Deal Engine → UNKNOWN
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>GDS → REFUNDED
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Blindly retrying the refund could therefore create a duplicate operation.&lt;/p>
&lt;p>For operations where the external system supports status queries or stable external references, the service will reconcile the operation before retrying.&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>TIMEOUT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>UNKNOWN
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Query GDS using
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>stable reference
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├──────────────┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼ ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>REFUNDED NOT FOUND
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼ ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>SUCCEEDED Retry
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Idempotency and reconciliation requirements will be documented separately for each business-critical workflow.&lt;/p>
&lt;h2 id="sns-usage">SNS Usage
&lt;/h2>&lt;p>SNS will &lt;strong>not&lt;/strong> automatically be placed in front of every SQS queue.&lt;/p>
&lt;p>For point-to-point asynchronous work:&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>Refund Service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SQS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Refund Worker
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>is sufficient.&lt;/p>
&lt;p>SNS will be introduced when an event has multiple independent consumers.&lt;/p>
&lt;p>For 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> RefundCompleted
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SNS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> / | \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> / | \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼ ▼ ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SQS Audit SQS Email SQS Analytics
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ │ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼ ▼ ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Audit Notify Analytics
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Service Service Service
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This prevents the producer from needing to know which systems consume the event.&lt;/p>
&lt;p>Each consumer receives its own SQS queue so that:&lt;/p>
&lt;ul>
&lt;li>consumers process events independently;&lt;/li>
&lt;li>one slow consumer does not block another;&lt;/li>
&lt;li>each consumer can define its own retry policy;&lt;/li>
&lt;li>each consumer can have its own DLQ;&lt;/li>
&lt;li>services can scale independently.&lt;/li>
&lt;/ul>
&lt;h2 id="alternatives-considered">Alternatives Considered
&lt;/h2>&lt;h3 id="apache-kafka">Apache Kafka
&lt;/h3>&lt;p>&lt;strong>Pros&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Excellent support for high-throughput event streaming.&lt;/li>
&lt;li>Strong event replay capabilities.&lt;/li>
&lt;li>Consumer groups allow multiple processing models.&lt;/li>
&lt;li>Long-lived event logs are useful for audit and event-driven architectures.&lt;/li>
&lt;li>Strong ecosystem for stream processing.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cons&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Introduces additional concepts and operational complexity.&lt;/li>
&lt;li>Requires Kafka-specific monitoring, partition management, consumer offset management, and schema governance.&lt;/li>
&lt;li>A managed Kafka platform would introduce another significant infrastructure dependency.&lt;/li>
&lt;li>Kafka&amp;rsquo;s strengths are not required for the initial asynchronous workloads identified.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Why rejected&lt;/strong>&lt;/p>
&lt;p>Deal Engine already relies heavily on AWS, and the immediate requirement is reliable asynchronous job and event processing rather than large-scale stream processing.&lt;/p>
&lt;p>SQS and SNS provide the required capabilities while integrating directly with the existing AWS environment and reducing operational overhead.&lt;/p>
&lt;p>Kafka may be reconsidered if future requirements include:&lt;/p>
&lt;ul>
&lt;li>high-volume event streaming;&lt;/li>
&lt;li>long-term event replay;&lt;/li>
&lt;li>event sourcing;&lt;/li>
&lt;li>stream processing;&lt;/li>
&lt;li>multiple consumers independently replaying historical events.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="rabbitmq">RabbitMQ
&lt;/h3>&lt;p>&lt;strong>Pros&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Mature messaging system.&lt;/li>
&lt;li>Flexible routing capabilities.&lt;/li>
&lt;li>Supports queues and publish/subscribe patterns.&lt;/li>
&lt;li>Familiar messaging semantics.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cons&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Introduces additional infrastructure to operate or another managed service dependency.&lt;/li>
&lt;li>Does not provide a significant advantage over AWS-native messaging for the identified use cases.&lt;/li>
&lt;li>Requires additional operational knowledge and monitoring.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Why rejected&lt;/strong>&lt;/p>
&lt;p>The platform is already AWS-centric. SQS and SNS provide the required messaging capabilities without introducing another messaging technology.&lt;/p>
&lt;hr>
&lt;h3 id="continue-using-synchronous-http">Continue Using Synchronous HTTP
&lt;/h3>&lt;p>&lt;strong>Pros&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Simple programming model.&lt;/li>
&lt;li>Existing team knowledge.&lt;/li>
&lt;li>Immediate responses.&lt;/li>
&lt;li>Straightforward debugging for simple request/response interactions.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cons&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Services remain runtime-coupled.&lt;/li>
&lt;li>Downstream failures can propagate upstream.&lt;/li>
&lt;li>Long-running GDS operations occupy request resources.&lt;/li>
&lt;li>Retry handling becomes difficult.&lt;/li>
&lt;li>Scaling producers and consumers independently is harder.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Why rejected as the default for asynchronous workflows&lt;/strong>&lt;/p>
&lt;p>Synchronous communication remains appropriate where an immediate result is required, but it should not be required for long-running or independently recoverable workflows.&lt;/p>
&lt;p>The architecture will therefore intentionally support both:&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>Need immediate answer?
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ┌───┴───┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> YES NO
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> HTTP SQS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Multiple consumers?
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ┌──┴──┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NO YES
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SQS SNS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SQS queues
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="consequences">Consequences
&lt;/h2>&lt;h3 id="positive">Positive
&lt;/h3>&lt;ul>
&lt;li>Reduced runtime coupling between services.&lt;/li>
&lt;li>Downstream outages do not necessarily cause upstream failures.&lt;/li>
&lt;li>Work can remain queued while consumers are temporarily unavailable.&lt;/li>
&lt;li>Consumers can scale independently.&lt;/li>
&lt;li>AWS manages the messaging infrastructure.&lt;/li>
&lt;li>Native integration with existing AWS infrastructure and monitoring.&lt;/li>
&lt;li>Built-in retry and dead-letter queue capabilities.&lt;/li>
&lt;li>SNS enables event fan-out without coupling producers to consumers.&lt;/li>
&lt;li>Supports gradual extraction of capabilities from the monolith.&lt;/li>
&lt;/ul>
&lt;h3 id="negative">Negative
&lt;/h3>&lt;ul>
&lt;li>Asynchronous workflows introduce eventual consistency.&lt;/li>
&lt;li>Message processing becomes more difficult to trace than synchronous calls.&lt;/li>
&lt;li>Consumers must be idempotent.&lt;/li>
&lt;li>Duplicate message delivery must be expected.&lt;/li>
&lt;li>Business workflows require explicit state management.&lt;/li>
&lt;li>Engineers must understand visibility timeout, retention, retry, DLQ, and delivery semantics.&lt;/li>
&lt;li>Debugging requires correlation IDs and distributed observability.&lt;/li>
&lt;/ul>
&lt;h2 id="risks">Risks
&lt;/h2>&lt;h3 id="duplicate-processing">Duplicate Processing
&lt;/h3>&lt;p>SQS Standard provides at-least-once delivery.&lt;/p>
&lt;p>&lt;strong>Mitigation:&lt;/strong> all business-critical consumers must implement idempotent processing using stable business identifiers.&lt;/p>
&lt;h3 id="poison-messages">Poison Messages
&lt;/h3>&lt;p>A malformed or permanently failing message could otherwise be retried repeatedly.&lt;/p>
&lt;p>&lt;strong>Mitigation:&lt;/strong> configure bounded retries and DLQs.&lt;/p>
&lt;h3 id="lost-database-to-queue-events">Lost Database-to-Queue Events
&lt;/h3>&lt;p>A service could persist a business operation and crash before publishing the corresponding SQS message:&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>BEGIN TRANSACTION
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>INSERT refund
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>COMMIT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>💥 crash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Send SQS message ← never happens
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The database and message broker do not participate in the same transaction.&lt;/p>
&lt;p>&lt;strong>Mitigation:&lt;/strong> use the Transactional Outbox Pattern for workflows where loss of the message would violate business guarantees.&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> SAME DB TRANSACTION
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>┌──────────────────────────────┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ INSERT refund │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ INSERT outbox_event │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>└──────────────┬───────────────┘
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ COMMIT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Outbox Publisher
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SQS
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="external-operation-succeeds-but-response-is-lost">External Operation Succeeds but Response Is Lost
&lt;/h3>&lt;p>Blind retrying could create duplicate external operations.&lt;/p>
&lt;p>&lt;strong>Mitigation:&lt;/strong> use stable external references, explicit &lt;code>UNKNOWN&lt;/code> states, provider-side status queries where available, and reconciliation before retrying uncertain operations.&lt;/p>
&lt;h3 id="poor-observability">Poor Observability
&lt;/h3>&lt;p>Asynchronous processing makes request chains less obvious.&lt;/p>
&lt;p>&lt;strong>Mitigation:&lt;/strong> propagate correlation IDs and trace context through messages and instrument producers and consumers using the platform&amp;rsquo;s standard observability tooling.&lt;/p>
&lt;h2 id="operational-requirements">Operational Requirements
&lt;/h2>&lt;p>Each production queue must define:&lt;/p>
&lt;ul>
&lt;li>queue owner;&lt;/li>
&lt;li>expected processing latency;&lt;/li>
&lt;li>visibility timeout;&lt;/li>
&lt;li>message retention period;&lt;/li>
&lt;li>retry policy;&lt;/li>
&lt;li>maximum receive count;&lt;/li>
&lt;li>DLQ;&lt;/li>
&lt;li>DLQ alarms;&lt;/li>
&lt;li>consumer concurrency;&lt;/li>
&lt;li>idempotency strategy;&lt;/li>
&lt;li>correlation/trace identifiers;&lt;/li>
&lt;li>recovery procedure.&lt;/li>
&lt;/ul>
&lt;p>CloudWatch alarms should detect conditions such as:&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>ApproximateAgeOfOldestMessage ↑
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Consumers may not be keeping up
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>DLQ Message Count &amp;gt; 0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Processing requires investigation
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="migration-strategy">Migration Strategy
&lt;/h2>&lt;p>SQS/SNS adoption will be incremental.&lt;/p>
&lt;p>Existing synchronous workflows will not be converted solely for architectural consistency.&lt;/p>
&lt;p>A workflow should move to asynchronous processing when there is a concrete benefit such as:&lt;/p>
&lt;ul>
&lt;li>long-running processing;&lt;/li>
&lt;li>external-system latency;&lt;/li>
&lt;li>retry requirements;&lt;/li>
&lt;li>temporary downstream unavailability;&lt;/li>
&lt;li>independent scaling;&lt;/li>
&lt;li>fan-out to multiple consumers;&lt;/li>
&lt;li>eventual consistency being acceptable.&lt;/li>
&lt;/ul>
&lt;p>For the monolith-to-services migration, a typical extraction can therefore evolve as:&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>PHASE 1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Monolith
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ HTTP
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Extracted Service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PHASE 2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Monolith
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SQS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Extracted Service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PHASE 3 — when fan-out is required
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Extracted Service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SNS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> / \
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼ ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SQS SQS
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼ ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Service Service
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> A B
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="success-criteria">Success Criteria
&lt;/h2>&lt;p>The decision will be considered successful when:&lt;/p>
&lt;ul>
&lt;li>asynchronous workflows continue processing after temporary consumer outages;&lt;/li>
&lt;li>duplicate message delivery does not produce duplicate business operations;&lt;/li>
&lt;li>failed messages are recoverable through DLQs;&lt;/li>
&lt;li>queue backlogs are observable and alertable;&lt;/li>
&lt;li>services can scale independently;&lt;/li>
&lt;li>GDS/external-system failures do not unnecessarily propagate to upstream services;&lt;/li>
&lt;li>new services can be extracted from the monolith without introducing a new messaging platform for each workflow.&lt;/li>
&lt;/ul>
&lt;h2 id="related-decisions">Related Decisions
&lt;/h2>&lt;ul>
&lt;li>ADR-0024: Idempotency Strategy for Asynchronous Consumers&lt;/li>
&lt;li>ADR-0025: Transactional Outbox for Reliable Event Publication&lt;/li>
&lt;li>ADR-0026: Retry and Dead-Letter Queue Strategy&lt;/li>
&lt;li>ADR-0027: Distributed Tracing for Asynchronous Workflows&lt;/li>
&lt;li>ADR-0028: GDS Timeout and Reconciliation Strategy&lt;/li>
&lt;/ul></description></item><item><title/><link>https://corebaseit.com/deal-engine/observavility-raw/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>contact@corebaseit.com (Vincent Bevia)</author><guid>https://corebaseit.com/deal-engine/observavility-raw/</guid><description>&lt;h1 id="observability-in-distributed-systems-from-technical-telemetry-to-business-confidence">Observability in Distributed Systems: From Technical Telemetry to Business Confidence
&lt;/h1>&lt;h2 id="executive-summary">Executive summary
&lt;/h2>&lt;p>Modern software systems rarely execute a business transaction inside a single application. A customer request may pass through an API, database, message broker, background worker and one or more external services before reaching its final state.&lt;/p>
&lt;p>This distribution improves scalability and resilience, but it also makes failures harder to understand. A technically successful API response may conceal a transaction that later becomes delayed, duplicated or stranded. A timeout may mean that an external operation failed—or that it succeeded but its response was lost.&lt;/p>
&lt;p>Observability provides the evidence required to understand these situations. It allows engineering teams to infer the internal state of a system from the telemetry it produces and to investigate questions that were not anticipated when the system was designed.&lt;/p>
&lt;p>Effective observability is not achieved merely by collecting logs or deploying dashboards. It requires:&lt;/p>
&lt;ul>
&lt;li>Structured and correlated telemetry&lt;/li>
&lt;li>End-to-end visibility across synchronous and asynchronous boundaries&lt;/li>
&lt;li>Business-state instrumentation&lt;/li>
&lt;li>Explicit reliability objectives&lt;/li>
&lt;li>Actionable alerting&lt;/li>
&lt;li>Recovery mechanisms for ambiguous outcomes&lt;/li>
&lt;/ul>
&lt;p>The ultimate goal is not simply to determine whether servers are running. It is to establish whether business operations are progressing correctly, explain failures efficiently and recover safely when the outcome is uncertain.&lt;/p>
&lt;hr>
&lt;h2 id="1-monitoring-tells-us-what-is-known-observability-supports-investigation">1. Monitoring tells us what is known; observability supports investigation
&lt;/h2>&lt;p>Traditional monitoring is usually built around predefined questions:&lt;/p>
&lt;ul>
&lt;li>Is CPU utilization too high?&lt;/li>
&lt;li>Is the API returning errors?&lt;/li>
&lt;li>Is the database connection pool exhausted?&lt;/li>
&lt;li>Is the queue growing?&lt;/li>
&lt;/ul>
&lt;p>These are important questions, but they are not sufficient for distributed systems.&lt;/p>
&lt;p>An engineer may instead need to investigate:&lt;/p>
&lt;ul>
&lt;li>Why are transactions taking longer for one category of customer?&lt;/li>
&lt;li>Did an external operation succeed before the connection was interrupted?&lt;/li>
&lt;li>Why was the same message processed twice?&lt;/li>
&lt;li>Which software or calculation version produced a disputed result?&lt;/li>
&lt;li>Where did a transaction stop progressing?&lt;/li>
&lt;/ul>
&lt;p>Observability supports this type of investigation by combining multiple telemetry signals and preserving the relationships between them.&lt;/p>
&lt;p>The central operating model is:&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>Metrics → Something is wrong
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Traces → Where it is going wrong
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Logs → What happened at that location
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>State → What the system believes about the business operation
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>OpenTelemetry describes traces, metrics and logs as complementary signals through which a system’s internal activity can be examined from different perspectives. Its observability primer also emphasizes the ability to investigate novel problems rather than only known failure conditions. &lt;a class="link" href="https://opentelemetry.io/docs/concepts/observability-primer/" target="_blank" rel="noopener"
>OpenTelemetry: Observability Primer&lt;/a>&lt;/p>
&lt;hr>
&lt;h2 id="2-the-core-observability-signals">2. The core observability signals
&lt;/h2>&lt;h3 id="21-logs-what-happened">2.1 Logs: what happened?
&lt;/h3>&lt;p>Logs record individual events. In production systems, they should be structured so that software can reliably search, validate and correlate them.&lt;/p>
&lt;p>A weak log message provides little diagnostic value:&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>Transaction failed
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A structured event is significantly more useful:&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-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;timestamp&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;2026-09-09T10:42:17.381Z&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;level&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;error&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;event&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;external_operation.timeout&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;service&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;transaction-worker&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;transactionId&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;TX-18472&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;traceId&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;4bf92f3577b34da6a3ce929d0e0e4736&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;spanId&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;00f067aa0ba902b7&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;messageId&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;MSG-8125&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;operation&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;refund&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;attempt&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">2&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;timeoutMs&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">5000&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;durationMs&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">5008&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;outcome&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;unknown_external_state&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This event explains which transaction was affected, which execution produced the error, where it occurred and how the system classified the outcome.&lt;/p>
&lt;p>A structured logging standard should define:&lt;/p>
&lt;ul>
&lt;li>Canonical event names&lt;/li>
&lt;li>Common field names and units&lt;/li>
&lt;li>Business and execution identifiers&lt;/li>
&lt;li>Error classifications&lt;/li>
&lt;li>State-transition fields&lt;/li>
&lt;li>Data-retention requirements&lt;/li>
&lt;li>Privacy and redaction rules&lt;/li>
&lt;/ul>
&lt;p>Sensitive information should not be placed in telemetry simply because it might be useful during an investigation. Credentials, tokens, personal information and complete transaction payloads require explicit handling policies.&lt;/p>
&lt;h3 id="22-metrics-how-often-and-how-much">2.2 Metrics: how often and how much?
&lt;/h3>&lt;p>Metrics aggregate system behaviour over time. They reveal trends, rates, distributions and changing conditions.&lt;/p>
&lt;p>Infrastructure metrics commonly include:&lt;/p>
&lt;ul>
&lt;li>CPU and memory utilization&lt;/li>
&lt;li>Request rate and error rate&lt;/li>
&lt;li>Response-time percentiles&lt;/li>
&lt;li>Database query latency&lt;/li>
&lt;li>Connection-pool saturation&lt;/li>
&lt;li>Queue depth&lt;/li>
&lt;li>Age of the oldest queued message&lt;/li>
&lt;li>Worker restarts&lt;/li>
&lt;li>Dead-letter queue size&lt;/li>
&lt;/ul>
&lt;p>These signals describe the platform, but they do not necessarily describe the customer outcome.&lt;/p>
&lt;p>A system can have normal CPU utilization while hundreds of transactions remain stuck. Business-oriented metrics are therefore equally important:&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>transactions_requested_total
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>transactions_completed_total
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>transactions_failed_total
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>transactions_unknown_total
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>transaction_processing_duration_seconds
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>transaction_retries_total
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>duplicate_messages_detected_total
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>idempotency_conflicts_total
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>reconciliation_mismatches_total
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The most valuable metrics connect technical behaviour to business flow. For 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>Requested: 10,000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── Completed: 9,650
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── Pending: 300
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └── Failed: 50
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This funnel reveals a problem that a server-health dashboard could easily miss.&lt;/p>
&lt;p>Metrics should use bounded dimensions such as operation, region, outcome or dependency category. Unique transaction, message and trace identifiers should not become metric labels because uncontrolled cardinality can make the monitoring platform expensive or unstable.&lt;/p>
&lt;h3 id="23-traces-where-did-it-happen">2.3 Traces: where did it happen?
&lt;/h3>&lt;p>A distributed trace represents the journey of one logical operation through multiple components. Each individual operation inside the trace is represented by a span.&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>Trace = the complete journey
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Span = one operation within that journey
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Consider a transaction processed asynchronously:&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>POST /transaction 45 ms
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── Database insert 8 ms
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>└── Publish message 11 ms
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ⋮
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ⋮ asynchronous queue delay
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ⋮
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Background worker 5.3 s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── Database read 7 ms
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── External service call 5.1 s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>└── Database update 12 ms
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The trace immediately identifies the external call as the dominant contributor to latency.&lt;/p>
&lt;p>Without tracing, an engineer may have to search API logs, identify a message, locate the responsible worker, correlate timestamps and then search external-integration logs. At scale, requests from many customers are interleaved, retries create additional executions and differences between system clocks introduce ambiguity.&lt;/p>
&lt;p>Tracing replaces this manual reconstruction with a causal graph.&lt;/p>
&lt;p>The W3C Trace Context standard defines interoperable HTTP headers for propagating trace identity between participating systems. Its &lt;code>traceparent&lt;/code> field identifies the trace and the calling operation, allowing downstream services to continue the same distributed trace. &lt;a class="link" href="https://www.w3.org/TR/trace-context/" target="_blank" rel="noopener"
>W3C Trace Context Recommendation&lt;/a>&lt;/p>
&lt;hr>
&lt;h2 id="3-correlation-requires-more-than-one-identifier">3. Correlation requires more than one identifier
&lt;/h2>&lt;p>Distributed systems commonly contain several identifiers with distinct purposes:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Identifier&lt;/th>
&lt;th>Question answered&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Business ID&lt;/td>
&lt;td>Which customer transaction is this?&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Trace ID&lt;/td>
&lt;td>Which distributed execution is this?&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Span ID&lt;/td>
&lt;td>Which individual operation is this?&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Message ID&lt;/td>
&lt;td>Which transport delivery is this?&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>External request ID&lt;/td>
&lt;td>Which operation does the external system recognize?&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>These identifiers should not be treated as interchangeable.&lt;/p>
&lt;p>A business transaction may produce multiple traces across its lifetime: initial submission, automatic retry, manual intervention and later reconciliation. The business identifier connects the complete history, while each trace describes a particular execution.&lt;/p>
&lt;p>A useful design persists durable business and external identifiers while propagating trace context between services. Relevant identifiers should appear together in structured logs so that engineers can move between business history, traces and detailed events.&lt;/p>
&lt;hr>
&lt;h2 id="4-observability-across-asynchronous-boundaries">4. Observability across asynchronous boundaries
&lt;/h2>&lt;p>Synchronous trace propagation is relatively direct:&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>Service A ── HTTP trace context ──► Service B
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Asynchronous communication introduces a break in time and execution:&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 ──► Message broker ── waits ──► Background worker
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The original request may have completed before the worker starts. To preserve causality, trace context must be injected into message metadata by the producer and extracted by the consumer.&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>Producer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── Create producer span
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── Attach trace context to message metadata
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └── Publish message
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Queue delay
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Consumer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── Extract trace context
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── Create consumer or processing span
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └── Record message and delivery attributes
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The system should measure queue delay separately from processing duration. Otherwise, a transaction that waited ten minutes before one second of processing may appear to have completed quickly.&lt;/p>
&lt;p>Retries, redeliveries, batches and fan-out patterns may not fit a strict parent-and-child relationship. Trace links can represent causal relationships without incorrectly implying that one operation executed directly inside another.&lt;/p>
&lt;hr>
&lt;h2 id="5-business-state-observability">5. Business-state observability
&lt;/h2>&lt;p>Technical telemetry becomes substantially more valuable when it is connected to the business state machine.&lt;/p>
&lt;p>An asynchronous transaction might follow this simplified lifecycle:&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>REQUESTED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>QUEUED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PROCESSING
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>EXTERNAL_REQUESTED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├────────► COMPLETED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├────────► FAILED
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └────────► UNKNOWN_EXTERNAL_STATE
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Every transition should produce durable and observable evidence:&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-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;event&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;transaction.state_transition&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;transactionId&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;TX-18472&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;from&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;EXTERNAL_REQUESTED&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;to&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;UNKNOWN_EXTERNAL_STATE&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;reason&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;response_timeout&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;attempt&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">1&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;recordVersion&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">7&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;calculationVersion&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;rules-2026-08&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;externalRequestId&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;EXT-94721&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;traceId&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;4bf92f3577b34da6a3ce929d0e0e4736&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This enables the platform to identify conditions such as:&lt;/p>
&lt;ul>
&lt;li>Transactions remaining in &lt;code>PROCESSING&lt;/code> too long&lt;/li>
&lt;li>Invalid or regressive transitions&lt;/li>
&lt;li>Excessive retry counts&lt;/li>
&lt;li>Concurrent modification conflicts&lt;/li>
&lt;li>Growing populations of unknown outcomes&lt;/li>
&lt;li>Results produced by an unexpected calculation version&lt;/li>
&lt;li>Differences between local and external state&lt;/li>
&lt;/ul>
&lt;p>This is a crucial distinction: observability should describe not only whether software components are available, but whether business operations are progressing correctly.&lt;/p>
&lt;hr>
&lt;h2 id="6-ambiguous-outcomes-when-a-timeout-is-not-a-failure">6. Ambiguous outcomes: when a timeout is not a failure
&lt;/h2>&lt;p>One of the most dangerous assumptions in distributed transaction processing is that a timeout means the operation failed.&lt;/p>
&lt;p>Consider the following sequence:&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>Local worker External system
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├──── Perform operation ──────►│
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ │ Operation succeeds
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │◄──── Success response ───────X Connection interrupted
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └──── Observes timeout
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The local system did not receive confirmation, but the external effect may already have occurred. Recording the transaction as failed and retrying it blindly could duplicate the effect.&lt;/p>
&lt;p>A more accurate state is:&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>UNKNOWN_EXTERNAL_STATE
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This state does not mean that the system is broken. It means that the available evidence is insufficient to assert success or failure.&lt;/p>
&lt;p>Managing this condition safely requires four complementary capabilities:&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>Observability
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> +
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Idempotency
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> +
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Explicit state
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> +
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Reconciliation
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Observability exposes what was attempted and why the result is ambiguous. Idempotency prevents repeated requests from creating additional effects. The state machine represents uncertainty honestly. Reconciliation later compares local state with external truth and resolves the discrepancy.&lt;/p>
&lt;p>Observability alone cannot make a distributed operation atomic. Its purpose is to provide the evidence needed for correctness and recovery mechanisms to work safely.&lt;/p>
&lt;hr>
&lt;h2 id="7-reliability-should-be-expressed-as-an-outcome">7. Reliability should be expressed as an outcome
&lt;/h2>&lt;p>A mature observability programme defines reliability in terms that users and business stakeholders can understand.&lt;/p>
&lt;p>Three related concepts are commonly used:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Service Level Indicator (SLI):&lt;/strong> the measured performance of the service.&lt;/li>
&lt;li>&lt;strong>Service Level Objective (SLO):&lt;/strong> the internal target for that measurement.&lt;/li>
&lt;li>&lt;strong>Service Level Agreement (SLA):&lt;/strong> a contractual commitment made to customers.&lt;/li>
&lt;/ul>
&lt;p>An outcome-oriented SLI might be:&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>Transactions reaching a known terminal state within five minutes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>─────────────────────────────────────────────────────────────────
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Eligible transaction requests
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The associated SLO could be:&lt;/p>
&lt;blockquote>
&lt;p>At least 99.0% of eligible transactions will reach a known terminal state within five minutes over a rolling 28-day period.&lt;/p>&lt;/blockquote>
&lt;p>This objective is more meaningful than stating that CPU utilization should remain below a threshold. Infrastructure metrics can help explain an SLO failure, but they are not the customer outcome.&lt;/p>
&lt;p>Google’s SRE guidance emphasizes that SLOs should be user-focused and supported by objective measurements and error budgets. &lt;a class="link" href="https://sre.google/resources/practices-and-processes/art-of-slos/" target="_blank" rel="noopener"
>Google SRE: The Art of SLOs&lt;/a>&lt;/p>
&lt;p>A complete SLO definition must specify:&lt;/p>
&lt;ul>
&lt;li>The eligible population&lt;/li>
&lt;li>The event that starts measurement&lt;/li>
&lt;li>What constitutes a good outcome&lt;/li>
&lt;li>The permitted duration&lt;/li>
&lt;li>The evaluation window&lt;/li>
&lt;li>Any exclusions&lt;/li>
&lt;li>How ambiguous outcomes are classified&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="8-alert-on-symptoms-that-require-action">8. Alert on symptoms that require action
&lt;/h2>&lt;p>An alert should indicate that a human needs to act. It should not merely report that something unusual occurred.&lt;/p>
&lt;p>Potentially actionable symptoms include:&lt;/p>
&lt;ul>
&lt;li>Rapid consumption of the SLO error budget&lt;/li>
&lt;li>Transactions remaining nonterminal beyond the expected duration&lt;/li>
&lt;li>A sustained increase in unknown external outcomes&lt;/li>
&lt;li>Material growth in the age of queued work&lt;/li>
&lt;li>Failure of reconciliation to resolve discrepancies&lt;/li>
&lt;li>Evidence that idempotency controls are no longer preventing duplicate effects&lt;/li>
&lt;/ul>
&lt;p>A temporary increase in CPU utilization may be useful diagnostic information, but it should not necessarily wake an engineer if customers remain unaffected.&lt;/p>
&lt;p>Google’s monitoring guidance distinguishes symptoms from causes and identifies latency, traffic, errors and saturation as four fundamental signals for user-facing services. It also recommends keeping paging rules actionable and low-noise. &lt;a class="link" href="https://sre.google/sre-book/monitoring-distributed-systems/" target="_blank" rel="noopener"
>Google SRE: Monitoring Distributed Systems&lt;/a>&lt;/p>
&lt;p>A useful alert includes:&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>Impact: 6.8% of transactions missed the five-minute objective
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Scope: one operation category in one region
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Related evidence: queue age rising; external timeouts elevated
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Links: dashboard, representative traces, runbook and recent changes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Action: verify processing progress and unknown-state accumulation
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This gives the responder a starting point instead of presenting an isolated threshold violation.&lt;/p>
&lt;hr>
&lt;h2 id="9-the-operational-investigation-path">9. The operational investigation path
&lt;/h2>&lt;p>A practical incident investigation should move from aggregate impact to authoritative business truth:&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>Metrics
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ Define scope and customer impact
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Traces
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ Identify the slow or failing stage
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Structured logs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ Explain decisions, errors and retries
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Business-state history
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │ Establish what the platform believes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ▼
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>External evidence and reconciliation
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Establish the actual outcome
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A typical investigation proceeds as follows:&lt;/p>
&lt;ol>
&lt;li>Confirm the customer-facing symptom and affected scope.&lt;/li>
&lt;li>Examine queue progress, processing latency and external dependencies.&lt;/li>
&lt;li>Select representative healthy and unhealthy traces.&lt;/li>
&lt;li>Identify the stage responsible for delay or failure.&lt;/li>
&lt;li>Inspect correlated logs for the relevant spans.&lt;/li>
&lt;li>Review the durable state-transition history.&lt;/li>
&lt;li>Resolve ambiguous outcomes through external evidence or reconciliation.&lt;/li>
&lt;li>Confirm that the SLI recovers and the backlog drains.&lt;/li>
&lt;li>Record instrumentation gaps and preventative improvements.&lt;/li>
&lt;/ol>
&lt;p>Temporal correlation alone is not proof of causality. Traces, deployment markers, state history and controlled tests should be combined before declaring a root cause.&lt;/p>
&lt;hr>
&lt;h2 id="10-a-practical-implementation-framework">10. A practical implementation framework
&lt;/h2>&lt;p>Organizations can develop observability incrementally.&lt;/p>
&lt;h3 id="phase-1-establish-semantic-consistency">Phase 1: Establish semantic consistency
&lt;/h3>&lt;p>Define canonical event names, field names, outcome classifications, identifiers and units. Standardization is more important than the choice of telemetry backend.&lt;/p>
&lt;h3 id="phase-2-instrument-the-critical-journey">Phase 2: Instrument the critical journey
&lt;/h3>&lt;p>Trace one important business operation from entry to terminal state. Include database access, message publication, queue delay, worker processing and external calls.&lt;/p>
&lt;h3 id="phase-3-add-business-state-telemetry">Phase 3: Add business-state telemetry
&lt;/h3>&lt;p>Measure state populations, state age, transition rates, retries, duplicate suppression and unknown outcomes.&lt;/p>
&lt;h3 id="phase-4-define-reliability-objectives">Phase 4: Define reliability objectives
&lt;/h3>&lt;p>Create SLIs and SLOs from customer outcomes. Document precisely what enters the calculation and what qualifies as success.&lt;/p>
&lt;h3 id="phase-5-build-the-investigation-workflow">Phase 5: Build the investigation workflow
&lt;/h3>&lt;p>Connect dashboard panels to representative traces, traces to structured logs and telemetry to durable business-state records.&lt;/p>
&lt;h3 id="phase-6-test-failure-and-recovery">Phase 6: Test failure and recovery
&lt;/h3>&lt;p>Deliberately exercise duplicate delivery, worker interruption, concurrent processing, external timeout and lost-response scenarios. Verify both the correctness mechanism and the evidence it produces.&lt;/p>
&lt;h3 id="phase-7-govern-cost-and-privacy">Phase 7: Govern cost and privacy
&lt;/h3>&lt;p>Establish budgets for metric cardinality, log volume, trace sampling and retention. Test redaction policies and monitor the telemetry pipeline itself for dropped data or broken propagation.&lt;/p>
&lt;hr>
&lt;h2 id="conclusion">Conclusion
&lt;/h2>&lt;p>Observability is a property of a well-designed system, not a product installed beside it.&lt;/p>
&lt;p>Logs, metrics and traces provide different views of technical behaviour. Their full value emerges when they are correlated with business identities, lifecycle state and recovery processes.&lt;/p>
&lt;p>For distributed transaction systems, the most important design principle is intellectual honesty: a system must distinguish what it knows from what it merely assumes. A timeout is not automatically a failure. A successful API response is not necessarily a completed business operation. A healthy server does not guarantee a healthy transaction flow.&lt;/p>
&lt;p>The objective is therefore broader than operational visibility:&lt;/p>
&lt;blockquote>
&lt;p>Every important transaction should be explainable, its correctness should be verifiable, and an ambiguous outcome should be recoverable.&lt;/p>&lt;/blockquote>
&lt;p>That is the difference between collecting telemetry and engineering for observability.&lt;/p>
&lt;hr>
&lt;h2 id="references">References
&lt;/h2>&lt;ol>
&lt;li>OpenTelemetry, &lt;a class="link" href="https://opentelemetry.io/docs/concepts/observability-primer/" target="_blank" rel="noopener"
>“Observability Primer”&lt;/a>.&lt;/li>
&lt;li>OpenTelemetry, &lt;a class="link" href="https://opentelemetry.io/docs/concepts/signals/" target="_blank" rel="noopener"
>“Signals”&lt;/a>.&lt;/li>
&lt;li>World Wide Web Consortium, &lt;a class="link" href="https://www.w3.org/TR/trace-context/" target="_blank" rel="noopener"
>“Trace Context”&lt;/a>.&lt;/li>
&lt;li>Google Site Reliability Engineering, &lt;a class="link" href="https://sre.google/sre-book/monitoring-distributed-systems/" target="_blank" rel="noopener"
>“Monitoring Distributed Systems”&lt;/a> and &lt;a class="link" href="https://sre.google/resources/practices-and-processes/art-of-slos/" target="_blank" rel="noopener"
>“The Art of SLOs”&lt;/a>.&lt;/li>
&lt;/ol>
&lt;p>Suggested SEO description:&lt;/p>
&lt;blockquote>
&lt;p>Learn how logs, metrics, distributed traces, business-state instrumentation, SLOs, idempotency and reconciliation combine to make modern distributed systems explainable and recoverable.&lt;/p>&lt;/blockquote></description></item></channel></rss>