<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Machine Mind</title>
    <subtitle>Practical notes on software architecture, backend engineering, cloud systems, Rust, Java, and developer tooling.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://saumilp.dev/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://saumilp.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-06T00:00:00+00:00</updated>
    <id>https://saumilp.dev/atom.xml</id>
    <entry xml:lang="en">
        <title>Custom Spring Boot Observability Starter: When Every Team&#x27;s Instrumentation Becomes Your Bottleneck</title>
        <published>2026-07-06T00:00:00+00:00</published>
        <updated>2026-07-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/blog/custom-spring-boot-observability-starter/"/>
        <id>https://saumilp.dev/blog/custom-spring-boot-observability-starter/</id>
        
        <content type="html" xml:base="https://saumilp.dev/blog/custom-spring-boot-observability-starter/">&lt;h2 id=&quot;the-problem&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-problem&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
The Problem&lt;&#x2F;h2&gt;
&lt;p&gt;Your telecom carrier processes 2 million call detail records (CDRs) per hour across six microservices: ingestion, validation, enrichment, rating, ledger, and billing. A customer calls complaining about an erroneous charge. You trace their call: it entered the pipeline at 14:32:17 UTC. By 14:32:22, it was marked BILLED. But somewhere between enrichment and rating—a 3-second window—the trace goes dark. Two disjoint traces where there should be one continuous flow.&lt;&#x2F;p&gt;
&lt;p&gt;You open Jaeger. The enrichment service shows the call’s trace ID. The rating service shows a &lt;em&gt;different&lt;&#x2F;em&gt; trace ID for the same call. Context was lost in an async enrichment callback that queued to Kafka. The enrichment team instrumented their callback with Micrometer &lt;code&gt;@Observed&lt;&#x2F;code&gt; but didn’t use &lt;code&gt;ContextPropagatingTaskDecorator&lt;&#x2F;code&gt;. The rating team did. Different approaches, both reasonable-looking, but they broke distributed tracing across team boundaries. Now you’ve spent 90 minutes debugging what should have been a 10-minute investigation.&lt;&#x2F;p&gt;
&lt;p&gt;This is what happens when you leave observability as a per-team concern. Each service adds its own &lt;code&gt;@Observed&lt;&#x2F;code&gt; annotations, uses slightly different context propagation logic, and tunes cardinality differently. The mesh is theoretically observable, but the traces are fragmented, context leaks at async boundaries, and your SLO dashboards show double-counted spans from overlapping instrumentation. When you’re processing millions of CDRs per day, every second of debug delay multiplies cost.&lt;&#x2F;p&gt;
&lt;p&gt;A custom observability starter solves this by making tracing and metrics a platform-level concern. Every service in your mesh gets the same opinion about what to instrument, how to propagate context, how to avoid cardinality explosions, and how to extend it without forking.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;why-build-vs-buy&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-build-vs-buy&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Why Build vs. Buy&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Vendor-provided auto-instrumentation&lt;&#x2F;strong&gt; (Datadog, New Relic, Splunk) solves observability. Their agents inject bytecode, intercept calls, and emit spans—no code changes needed. You pay per GB and get their UI bundled.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Why we built instead:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The big cloud observability vendors are optimized for black-box applications. They emit &lt;em&gt;a lot&lt;&#x2F;em&gt; of data—every DB query, every HTTP call, every exception. In a telecom context, that cardinality explodes. A CDR (call detail record) processing pipeline might touch a customer ID dimension table on every row. Auto-instrumentation sees that as 2 million independent queries per hour and emits 2 million spans per hour. Your cardinality limits hit in minutes.&lt;&#x2F;p&gt;
&lt;p&gt;You also lose control. Your vendor’s defaults might capture PII or highly sensitive telecom fields (calling line ID, called number, IMSI&#x2F;IMEI). You can filter in their UI, but that’s reactive. You want to decide &lt;em&gt;at instrumentation time&lt;&#x2F;em&gt; what gets captured. For regulatory compliance (TCPA, GDPR), that matters.&lt;&#x2F;p&gt;
&lt;p&gt;Finally: &lt;strong&gt;you own the code&lt;&#x2F;strong&gt;. When a bug in the starter breaks observability for all your services, you’re the on-call engineer. But you’re also the one who can ship a fix in 30 minutes instead of waiting for a vendor release and staging environment validation. For a platform team, that ownership is often the right trade-off.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The downside:&lt;&#x2F;strong&gt; you maintain it. Every minor Spring Boot release, every Micrometer version bump, you validate the starter still works. A critical bug in the starter affects every service in your mesh. The context propagation logic has to cover Reactor, @Async, virtual threads, and ForkJoinPool—that’s a surface area.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;architectural-vision&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#architectural-vision&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Architectural Vision&lt;&#x2F;h2&gt;
&lt;p&gt;The starter sits between your application code and the Micrometer Observation + OpenTelemetry layers. It’s not a replacement for either—it’s a wrapper that enforces consistent defaults and provides extension points so teams can override without forking.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;mermaid&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;graph TD&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    subgraph App[&amp;quot;Application Layer&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        AppCode[&amp;quot;Spring Boot App&amp;lt;br&#x2F;&amp;gt;@Transactional @Observed&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    subgraph Starter[&amp;quot;Observability Starter&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Config[&amp;quot;Auto-Configuration&amp;lt;br&#x2F;&amp;gt;@ConditionalOnMissingBean&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Context[&amp;quot;Context Propagation&amp;lt;br&#x2F;&amp;gt;Reactor + @Async&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Extension[&amp;quot;Extension Points&amp;lt;br&#x2F;&amp;gt;Predicate&#x2F;Filter&#x2F;Handler&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    subgraph Micrometer[&amp;quot;Micrometer &amp;amp; OpenTelemetry&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        MM[&amp;quot;Micrometer&amp;lt;br&#x2F;&amp;gt;Observation API&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        OTel[&amp;quot;OpenTelemetry&amp;lt;br&#x2F;&amp;gt;Exporters&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    subgraph Backends[&amp;quot;Observability Backends&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Jaeger[&amp;quot;Jaeger&#x2F;Tempo&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Prometheus[&amp;quot;Prometheus&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    AppCode --&amp;gt; Config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    AppCode --&amp;gt; Extension&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Config --&amp;gt; MM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Context --&amp;gt; MM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Extension --&amp;gt; MM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    MM --&amp;gt; OTel&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    OTel --&amp;gt; Jaeger&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    OTel --&amp;gt; Prometheus&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The starter provides three things:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Sensible defaults&lt;&#x2F;strong&gt; — every service gets the same &lt;code&gt;@Observed&lt;&#x2F;code&gt; setup on HTTP handlers, repository methods, and async tasks.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Context propagation&lt;&#x2F;strong&gt; — trace IDs flow from sync code into Reactor &lt;code&gt;Context&lt;&#x2F;code&gt;, into &lt;code&gt;@Async&lt;&#x2F;code&gt; tasks, even into &lt;code&gt;ForkJoinPool&lt;&#x2F;code&gt; work-stealing threads.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Extension points&lt;&#x2F;strong&gt; — teams can define &lt;code&gt;ObservationPredicates&lt;&#x2F;code&gt; to exclude certain paths, &lt;code&gt;ObservationHandlers&lt;&#x2F;code&gt; to extract custom attributes, and &lt;code&gt;ObservationFilters&lt;&#x2F;code&gt; to rename or suppress spans without editing the starter source.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;key-design-decisions&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#key-design-decisions&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Key Design Decisions&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Auto-configuration strategy&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Spring Boot’s &lt;code&gt;@ConditionalOnClass&lt;&#x2F;code&gt;, &lt;code&gt;@ConditionalOnMissingBean&lt;&#x2F;code&gt; idiom is the foundation. The starter auto-configures:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ObservationRegistry&lt;&#x2F;code&gt; customization (registers filters, predicates, handlers)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;TraceIdGenerator&lt;&#x2F;code&gt; (defaults to OpenTelemetry’s UUID-based generator)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;ContextPropagator&lt;&#x2F;code&gt; beans for W3C Trace Context and W3C Baggage&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;MeterRegistry&lt;&#x2F;code&gt; instrumentation (CPU, memory, GC)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Teams can override any bean by defining their own in &lt;code&gt;application.yml&lt;&#x2F;code&gt; or a &lt;code&gt;@Configuration&lt;&#x2F;code&gt; class. No forking required.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The observation lifecycle&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here’s what happens when a span is created:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;mermaid&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sequenceDiagram&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    participant Request as HTTP Request&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    participant App as App Code&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    participant Registry as Observation Registry&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    participant Predicate as Predicate&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    participant Filter as Filter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    participant Handler as Handler&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    participant Backend as Backend&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Request -&amp;gt;&amp;gt; App: HTTP handler&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    App -&amp;gt;&amp;gt; Registry: Observation.start()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Registry -&amp;gt;&amp;gt; Predicate: test(name, context)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    alt Predicate rejects&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Predicate --&amp;gt;&amp;gt; Registry: false&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Registry --&amp;gt;&amp;gt; App: no-op observation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    else Predicate accepts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Predicate -&amp;gt;&amp;gt; Registry: true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Registry -&amp;gt;&amp;gt; Filter: process(scope)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Filter -&amp;gt;&amp;gt; Handler: handle(scope)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Handler -&amp;gt;&amp;gt; Backend: emit span&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        App -&amp;gt;&amp;gt; App: business logic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        App -&amp;gt;&amp;gt; Registry: Observation.stop()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Registry -&amp;gt;&amp;gt; Handler: handle(context)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Handler -&amp;gt;&amp;gt; Backend: finalize span&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Backend --&amp;gt;&amp;gt; App: done&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Context propagation across async boundaries&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The hardest part: trace IDs must flow from the original HTTP thread into async tasks. Spring Boot 3.0+ provides &lt;code&gt;TaskExecutor&lt;&#x2F;code&gt; integration via &lt;code&gt;ContextPropagatingTaskDecorator&lt;&#x2F;code&gt;, but you have to opt in &lt;em&gt;per threadpool&lt;&#x2F;em&gt;. The starter makes it automatic.&lt;&#x2F;p&gt;
&lt;p&gt;When you define &lt;code&gt;@Bean public TaskExecutor&lt;&#x2F;code&gt; in your application, the starter wraps it with &lt;code&gt;ContextPropagatingTaskDecorator&lt;&#x2F;code&gt; unless you’ve already done so. Same for &lt;code&gt;ThreadPoolExecutor&lt;&#x2F;code&gt; used in &lt;code&gt;@Async&lt;&#x2F;code&gt; methods.&lt;&#x2F;p&gt;
&lt;p&gt;For Reactor (the hard case), the starter registers a &lt;code&gt;ContextPropagationContext&lt;&#x2F;code&gt; supplier that hooks into Reactor’s &lt;code&gt;Context&lt;&#x2F;code&gt; API. When a trace scope is created, its attributes (trace ID, span ID) are stored in Reactor’s &lt;code&gt;Context&lt;&#x2F;code&gt; automatically. Downstream operators see them.&lt;&#x2F;p&gt;
&lt;p&gt;The trade-off: automatic wrapping assumes you want context propagated everywhere. If you have hot loops doing &lt;code&gt;@Async&lt;&#x2F;code&gt; work millions of times per second, the &lt;code&gt;TaskDecorator&lt;&#x2F;code&gt; overhead (copying the context map) matters. You can disable it for specific thread pools via properties and manage context manually.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;extension-points&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#extension-points&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Extension Points&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;ObservationPredicate&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Decides whether an observation scope should be recorded. Use it to exclude health checks, readiness probes, and other noisy low-value calls.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Bean&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; ObservationPredicate&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; observationPredicate&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Skip &#x2F;actuator&#x2F;* paths&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http.server&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;        context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;getAttribute&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http.url&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; null&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;      String&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; url&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;getAttribute&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http.url&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;      if&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;url&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;startsWith&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;actuator&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; false&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;ObservationFilter&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Mutates attributes on an observation before it’s recorded. Rename attributes, add custom tags, suppress high-cardinality values.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Bean&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; ObservationFilter&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; observationFilter&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Bucket transaction amounts to $1K ranges&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;getAttribute&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;transaction.amount&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; null&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;      long&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; amount&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;Long&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;getAttribute&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;transaction.amount&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;      long&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; bucket&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;amount &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1000&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1000&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;      context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;addAttribute&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;transaction.amount.bucket&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; bucket&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;ObservationHandler&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Custom code that runs when a scope is created, stopped, or encounters an error. Use it to log, send alerts, or update custom metrics.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Bean&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; ObservationHandler&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;Observation&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;Context&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; customHandler&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; new&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; ObservationHandler&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;Observation&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;Context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Override&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;    public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; void&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; onStart&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;Observation&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;Context&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;      &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Custom logic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Override&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;    public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; void&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; onStop&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;Observation&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;Context&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;      &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Custom logic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Override&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;    public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; boolean&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; supportsContext&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;Observation&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;Context&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;      return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;ObservationRegistryCustomizer&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Fine-grained control: add or remove handlers, set sampler strategies, configure exporters.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Bean&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; ObservationRegistryCustomizer&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;MeterRegistry&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; registryCustomizer&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;registry&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    registry&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;observationConfig&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;      .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;observationHandler&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;myHandler&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;      .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;sampler&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;Sampler&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;alwaysSample&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt; &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Override default sampler&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;The extension point model:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ObservationPredicate&lt;&#x2F;code&gt; — decides whether to record&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;ObservationFilter&lt;&#x2F;code&gt; — mutates attributes before recording&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;ObservationHandler&lt;&#x2F;code&gt; — runs custom code on lifecycle events&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;ObservationRegistryCustomizer&lt;&#x2F;code&gt; — configures the registry itself&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Teams implement these as beans and the starter discovers them automatically via &lt;code&gt;@ConditionalOnMissingBean&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;reactive-async-propagation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#reactive-async-propagation&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Reactive &amp;amp; Async Propagation&lt;&#x2F;h2&gt;
&lt;p&gt;This is where teams diverge most. Reactive code (Reactor, Project Loom virtual threads) and traditional &lt;code&gt;@Async&lt;&#x2F;code&gt; need different context propagation strategies.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Reactor (the easy case now, hard historically)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Spring Framework 6.1+ and Spring Boot 3.2+ integrated Micrometer’s &lt;code&gt;ContextPropagationContext&lt;&#x2F;code&gt; directly into Reactor. When you create an observation scope, its attributes are automatically stored in Reactor’s &lt;code&gt;Context&lt;&#x2F;code&gt; and flow through all downstream operators (map, flatMap, switchMap). No manual plumbing.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt; class&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; OrderService&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;  @&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Autowired&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt; private&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; Observation&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; observation&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;  public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; Mono&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;Order&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; createOrder&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;Order&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; order&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Observation scope is created; trace ID flows into Reactor context&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; observation&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;observe&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;      orderRepository&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;save&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;order&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;flatMap&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;saved &lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;          &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Trace ID is visible here&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;          notificationService&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;notify&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;saved&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;        )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    )&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;@Async (the complex case)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;@Async&lt;&#x2F;code&gt; methods run on a thread pool (&lt;code&gt;TaskExecutor&lt;&#x2F;code&gt;). Without context propagation, the trace ID stays on the caller’s thread. The async work is invisible to tracing.&lt;&#x2F;p&gt;
&lt;p&gt;The starter wraps your &lt;code&gt;TaskExecutor&lt;&#x2F;code&gt; with &lt;code&gt;ContextPropagatingTaskDecorator&lt;&#x2F;code&gt;, which copies the &lt;code&gt;MDC&lt;&#x2F;code&gt; and Spring’s &lt;code&gt;SecurityContext&lt;&#x2F;code&gt; (and, in Spring 6.1+, &lt;code&gt;ContextPropagationContext&lt;&#x2F;code&gt;) to the thread pool thread. The trace ID follows.&lt;&#x2F;p&gt;
&lt;p&gt;But there’s a catch: &lt;code&gt;ForkJoinPool&lt;&#x2F;code&gt; (used by &lt;code&gt;CompletableFuture&lt;&#x2F;code&gt; and &lt;code&gt;parallelStream()&lt;&#x2F;code&gt;) is also a &lt;code&gt;Executor&lt;&#x2F;code&gt;, but Spring doesn’t auto-wrap it. If your service uses &lt;code&gt;CompletableFuture&lt;&#x2F;code&gt; for async work, the starter can’t help—context won’t propagate. You have to either:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Avoid &lt;code&gt;CompletableFuture&lt;&#x2F;code&gt;; use &lt;code&gt;@Async&lt;&#x2F;code&gt; instead.&lt;&#x2F;li&gt;
&lt;li&gt;Manually wrap your &lt;code&gt;CompletableFuture&lt;&#x2F;code&gt; chains with context copy&#x2F;restore logic (tedious).&lt;&#x2F;li&gt;
&lt;li&gt;Accept that &lt;code&gt;ForkJoinPool&lt;&#x2F;code&gt; work loses trace context (the pragmatic choice for non-critical paths).&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Real example: a telecom billing system processes CDRs in parallel using &lt;code&gt;parallelStream()&lt;&#x2F;code&gt; on a &lt;code&gt;ForkJoinPool&lt;&#x2F;code&gt;. The main trace covers the CDR ingestion. Each record’s processing (validation, enrichment, ledger update) runs on the &lt;code&gt;ForkJoinPool&lt;&#x2F;code&gt; and loses the trace context. The starter can’t help. Solution: convert to a &lt;code&gt;TaskExecutor&lt;&#x2F;code&gt;-backed &lt;code&gt;@Async&lt;&#x2F;code&gt; loop, accept the sequential overhead, and regain tracing.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;data-access-layer-coverage&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#data-access-layer-coverage&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Data Access Layer Coverage&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;JDBC DataSource (the problem)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;javax.sql.DataSource&lt;&#x2F;code&gt; is wrapped by Spring with a &lt;code&gt;JdbcObservationFilter&lt;&#x2F;code&gt;, which emits spans for each SQL query. Default behavior captures the SQL and result count. But on a high-throughput service, every unique SQL query becomes a cardinality explosion.&lt;&#x2F;p&gt;
&lt;p&gt;Example: a payments ledger service runs &lt;code&gt;SELECT * FROM ledger WHERE account_id = ?&lt;&#x2F;code&gt; millions of times per day. Each account_id is a different query &lt;em&gt;in the span attributes&lt;&#x2F;em&gt;. You hit your cardinality limits in hours.&lt;&#x2F;p&gt;
&lt;p&gt;The starter lets you filter or rename these attributes:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Bean&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; ObservationFilter&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; jdbcFilter&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;sql.query&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;equals&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;getName&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;      &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Remove the WHERE clause; just keep the table name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;      String&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; sql&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;String&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;getAttribute&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;sql.query&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;      String&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; normalized&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; sql&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;replaceAll&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;WHERE.*&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;WHERE ...&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;intern&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;      context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;addAttribute&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;sql.query&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; normalized&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;R2DBC (the solution)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;R2DBC (reactive relational database connectivity) ships with first-class Micrometer Observation support. Statements are wrapped automatically, span attributes are pre-normalized, and cardinality is sane.&lt;&#x2F;p&gt;
&lt;p&gt;Trade-off: not all JDBC drivers have R2DBC equivalents (Oracle’s R2DBC support came late). If you’re on PostgreSQL or MySQL, R2DBC is available and observability is better. If you’re on an older proprietary database, you’re stuck with JDBC.&lt;&#x2F;p&gt;
&lt;p&gt;The starter doesn’t force R2DBC, but it strongly recommends it and configures JDBC cardinality filtering to match R2DBC’s behavior.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Data access observability paths:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JDBC Path:&lt;&#x2F;strong&gt; Raw SQL + account_id in spans → cardinality explosion&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;JDBC + Starter Filter:&lt;&#x2F;strong&gt; Normalized SQL → sane cardinality&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;R2DBC Path:&lt;&#x2F;strong&gt; Native support, pre-normalized → sane cardinality by default&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;rollout-plan&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#rollout-plan&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Rollout Plan&lt;&#x2F;h2&gt;
&lt;p&gt;You don’t flip a switch and add the starter to 200 services overnight. Context propagation bugs, cardinality explosions, and extension point conflicts will surface only under production load.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1: Adoption by one team (weeks 1–4)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The platform team dogfoods the starter in one non-critical service (e.g., user preferences microservice). Goals:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Verify context propagation works with their tech stack (Spring Cloud Gateway, Reactor, PostgreSQL).&lt;&#x2F;li&gt;
&lt;li&gt;Validate that Jaeger&#x2F;Tempo shows correct traces.&lt;&#x2F;li&gt;
&lt;li&gt;Load-test to catch cardinality issues early.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Learnings: the preferences team uses &lt;code&gt;CompletableFuture&lt;&#x2F;code&gt; in a recommendations enrichment pipeline. Context is lost. They either switch to &lt;code&gt;@Async&lt;&#x2F;code&gt; or accept the tracing gap. Decision: accept it (it’s low-priority work).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2: Broader adoption (weeks 5–12)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Roll out to 3–5 services across different teams: payments, ledger, notifications, compliance. Each team configures the starter slightly differently (different &lt;code&gt;ObservationPredicates&lt;&#x2F;code&gt;, different sampler strategies).&lt;&#x2F;p&gt;
&lt;p&gt;Learnings:&lt;&#x2F;p&gt;
&lt;p&gt;The ledger team’s observations on &lt;code&gt;UPDATE account SET balance = balance + ?&lt;&#x2F;code&gt; create one span per unique balance delta. High-cardinality disaster. The starter’s &lt;code&gt;ObservationFilter&lt;&#x2F;code&gt; saves them; they normalize deltas to $100 buckets.&lt;&#x2F;p&gt;
&lt;p&gt;The payments team runs two rate-limiting endpoints side by side (old and new). Both are instrumented. Overlapping &lt;code&gt;@Observed&lt;&#x2F;code&gt; annotations cause spans to be double-counted in Prometheus. Solution: add an &lt;code&gt;ObservationPredicate&lt;&#x2F;code&gt; to suppress the old endpoint’s observations.&lt;&#x2F;p&gt;
&lt;p&gt;The compliance service has audit logging (&lt;code&gt;@Observed&lt;&#x2F;code&gt; on every repository method) and fine-grained tracking (another layer of observability for reporting). Too many observations, Prometheus metrics grow 10x. Solution: introduce a sampler that records 10% of low-cardinality compliance observations and 100% of high-cardinality suspicious transaction flags.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Phase 3: Ecosystem-wide (months 4–6)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Roll out to all services. By this point, the starter has stabilized, and the platform team has published a runbook: “What to do when your observations aren’t showing up”, “How to fix cardinality explosions”, “When to use predicates vs. filters”.&lt;&#x2F;p&gt;
&lt;p&gt;Real example: a major telecom processing 10 million CDRs per day rolls out the starter across their main pipeline (ingestion, validation, enrichment, ledger, billing). Initial cardinality issues: validation step touches 50K equipment IDs per second. They filter observations to suppress equipment-specific tags. Ledger step touches customer service plans (40K plans). They normalize to plan buckets. After 4 weeks of tuning, observability is good, cardinality is predictable.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;trade-offs-failure-modes&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#trade-offs-failure-modes&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Trade-offs &amp;amp; Failure Modes&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Duplicate observations (silent SLO corruption)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If two teams instrument the same code path, you get two spans for one operation. Prometheus metrics double-count duration and error rates.&lt;&#x2F;p&gt;
&lt;p&gt;Example: the ledger team adds &lt;code&gt;@Observed&lt;&#x2F;code&gt; to &lt;code&gt;TransactionRepository.save()&lt;&#x2F;code&gt;. Later, the core platform team adds the same annotation in a base repository class. Now both fire. Metrics for save duration look good, but they’re double. SLO alerts fire because “p99 duration increased”, but it’s an instrumentation artifact.&lt;&#x2F;p&gt;
&lt;p&gt;Detection: compare span counts in Jaeger to API call counts. If spans &amp;gt; calls, you have duplication.&lt;&#x2F;p&gt;
&lt;p&gt;Fix: the starter includes an optional &lt;code&gt;DuplicateObservationDetector&lt;&#x2F;code&gt; that logs when two observations with the same name are active on the same thread. It’s disabled by default (low-overhead but not free) and can be enabled per service.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; In application.yml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;pring.observability.duplicate-detection-enabled&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language z-boolean&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Missing context in async retry queues&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A payments service publishes a retry event to Kafka when a ledger update fails. The consumer reads the event, but the trace ID is not in the Kafka header (because the producer didn’t serialize it). The consumer starts a fresh trace. Two disjoint traces where there should be one.&lt;&#x2F;p&gt;
&lt;p&gt;Fix: the starter provides an &lt;code&gt;ObservationContextMessagePropagator&lt;&#x2F;code&gt; bean that automatically serializes trace context into Kafka headers (via Spring Cloud Stream &#x2F; Spring Kafka).&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Automatic, but only if you use Spring Kafka or Spring Cloud Stream&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;pring.application.name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ayments-service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; The starter detects Kafka and adds the propagator&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Cardinality explosion from normalized-too-late filters&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You define an &lt;code&gt;ObservationFilter&lt;&#x2F;code&gt; that normalizes high-cardinality values. But the span is already created and exported before the filter runs. You’re still emitting millions of unique metric names to Prometheus.&lt;&#x2F;p&gt;
&lt;p&gt;Lesson: use &lt;code&gt;ObservationPredicate&lt;&#x2F;code&gt; (which runs before observation creation) to prevent high-cardinality scopes from being recorded at all. Use &lt;code&gt;ObservationFilter&lt;&#x2F;code&gt; only to tag transformations, not to suppress.&lt;&#x2F;p&gt;
&lt;p&gt;Wrong (ineffective):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Bean&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; ObservationFilter&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; wrongApproach&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; context &lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;isTooHighCardinality&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;      &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Too late! Span already created and emitted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;      context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;addAttribute&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;filtered&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Right (efficient):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Bean&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; ObservationPredicate&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; rightApproach&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;equals&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;sql.query&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; isTooHighCardinality&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;context&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;      return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; false&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt; &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Don&amp;#39;t record this observation at all&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Virtual threads and context leaks&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;With Project Loom’s virtual threads, you can create millions of threads with negligible overhead. But if context propagation isn’t set up correctly, each virtual thread gets a copy of the trace context, and you lose the parent-child relationship in Jaeger.&lt;&#x2F;p&gt;
&lt;p&gt;Spring 6.1+ handles this automatically via &lt;code&gt;ContextPropagationContext&lt;&#x2F;code&gt; and &lt;code&gt;VirtualThreadTaskExecutor&lt;&#x2F;code&gt;. The starter configures it. But if you use raw &lt;code&gt;Thread.startVirtualThread()&lt;&#x2F;code&gt; without the Spring abstraction, context doesn’t flow.&lt;&#x2F;p&gt;
&lt;p&gt;Mitigation: the starter docs emphasize using Spring’s &lt;code&gt;TaskExecutor&lt;&#x2F;code&gt; abstraction, not raw threads. For teams that ignore this, tracing gaps appear in Jaeger.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;cost-ownership&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#cost-ownership&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Cost &amp;amp; Ownership&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Who maintains this?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The platform team. It’s a platform investment, not a framework. Three engineers: one for Micrometer&#x2F;OpenTelemetry knowledge, one for Spring Boot and context propagation, one for incident response.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;How is it distributed?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Publish to your internal Artifactory &#x2F; Nexus as &lt;code&gt;com.yourcompany.platform:spring-boot-starter-observability:1.0.0&lt;&#x2F;code&gt;. Every service includes it as a &lt;code&gt;compileOnly&lt;&#x2F;code&gt; or &lt;code&gt;implementation&lt;&#x2F;code&gt; dependency.&lt;&#x2F;p&gt;
&lt;p&gt;Versioning: semantic versioning. Minor version bumps when you add new extension points. Patch bumps for bug fixes. When Spring Boot or Micrometer releases a major version, you bump the starter’s major version and validate it across a tier of services before rolling out.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Failure mode: the starter has a bug&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A critical bug in context propagation logic is discovered: trace IDs are being dropped in 0.01% of async tasks (enough to break financial reconciliation). You cut a patch release, publish to Artifactory, and all services pick it up in their next deploy.&lt;&#x2F;p&gt;
&lt;p&gt;If you’re using a vendored observability agent, this is the vendor’s problem. With a custom starter, it’s yours. You handle the incident in 2 hours. That’s the ownership cost.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Failure mode: the starter has a breaking change&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You upgrade to Micrometer 1.13, which changes the &lt;code&gt;ObservationHandler&lt;&#x2F;code&gt; interface signature. The starter is compatible, but your team’s custom &lt;code&gt;ObservationHandler&lt;&#x2F;code&gt; breaks. You have to backport the custom handler to the new API, or the service can’t deploy.&lt;&#x2F;p&gt;
&lt;p&gt;This is why the starter is versioned carefully. You don’t bump Micrometer versions in the starter without extensive validation and a clear upgrade path for custom handlers.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;closing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#closing&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Closing&lt;&#x2F;h2&gt;
&lt;p&gt;If I were to start over, I’d push harder on the separation between “what to observe by default” and “how teams override those defaults”. We learned this too late: a single &lt;code&gt;application.properties&lt;&#x2F;code&gt; file with 47 observability flags is harder to maintain than a few well-designed extension points. Teams are smarter than you think. Give them the levers, set reasonable defaults, and they’ll extend it without breaking things.&lt;&#x2F;p&gt;
&lt;p&gt;Also: observe in production, not in development. Early emphasis on “let’s instrument everything in the test environment” wasted weeks. Real issues surface when you’re processing millions of transactions per day, and that’s when you need to tune. Start with broad observations, filter aggressively, and add precision where it matters.&lt;&#x2F;p&gt;
&lt;p&gt;The other thing: context propagation is harder than metrics. Metrics are fire-and-forget. Context &lt;em&gt;has&lt;&#x2F;em&gt; to flow through async boundaries, and there are always new frameworks (Vert.x, Kotlin coroutines, custom thread pools) that break it. You can’t predict all of them. The starter provides good defaults, but every new technology that touches async gets a post-mortem: “Why didn’t trace context follow?”&lt;&#x2F;p&gt;
&lt;p&gt;Still, building this saved the organization millions in observability tooling costs and weeks of debugging time. A platform team that owns observability is worth the investment.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;references&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#references&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
References&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;opentelemetry.io&#x2F;docs&#x2F;instrumentation&#x2F;java&#x2F;automatic&#x2F;spring-boot&#x2F;&quot;&gt;OpenTelemetry Java Automatic Spring Boot Instrumentation&lt;&#x2F;a&gt; — Latest automatic instrumentation guidance from OpenTelemetry, confirms bytecode-injection patterns and OpenTelemetry exporter setup.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;micrometer.io&#x2F;docs&#x2F;observation&quot;&gt;Micrometer Observation API&lt;&#x2F;a&gt; — Core API reference for &lt;code&gt;ObservationPredicate&lt;&#x2F;code&gt;, &lt;code&gt;ObservationHandler&lt;&#x2F;code&gt;, &lt;code&gt;ObservationFilter&lt;&#x2F;code&gt;, and registry customization.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.spring.io&#x2F;spring-boot&#x2F;docs&#x2F;current&#x2F;reference&#x2F;html&#x2F;actuator.html&quot;&gt;Spring Boot Actuator Observability&lt;&#x2F;a&gt; — Spring Boot’s native observability integration, context propagation, and async handler configuration.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;spring.io&#x2F;blog&#x2F;2023&#x2F;10&#x2F;02&#x2F;context-propagation-in-spring-framework-6-1&quot;&gt;Spring Framework 6.1 Context Propagation&lt;&#x2F;a&gt; — Virtual thread and Reactor context propagation design (referenced via OpenTelemetry docs, confirmed in Spring 6.1+ release notes).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;r2dbc.io&#x2F;&quot;&gt;R2DBC Observability&lt;&#x2F;a&gt; — R2DBC’s native Micrometer integration and cardinality control compared to JDBC.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Solving the Dual-Write Problem with Spring Boot Starter Outbox</title>
        <published>2026-07-05T00:00:00+00:00</published>
        <updated>2026-07-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/blog/spring-boot-starter-outbox/"/>
        <id>https://saumilp.dev/blog/spring-boot-starter-outbox/</id>
        
        <content type="html" xml:base="https://saumilp.dev/blog/spring-boot-starter-outbox/">&lt;h2 id=&quot;the-problem&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-problem&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
The Problem&lt;&#x2F;h2&gt;
&lt;p&gt;Publishing messages to a message broker inside a database transaction is unreliable. If the transaction rolls back after publishing, you have a phantom message. If publishing fails after the transaction commits, you have a lost message.&lt;&#x2F;p&gt;
&lt;p&gt;Consider this flow in a typical microservice:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Your database — source of truth for business state (orders, users, shipments)&lt;&#x2F;li&gt;
&lt;li&gt;Your message broker — communication channel to other services (Kafka topics, RabbitMQ exchanges)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;When an order is placed, you naturally want to save it to the database AND publish an event to notify other services. But this naive approach has a critical race condition:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Broker publish succeeds, then DB transaction rolls back → consumers see an order that never existed (phantom message)&lt;&#x2F;li&gt;
&lt;li&gt;DB transaction commits, then broker publish fails → consumers never hear about the order (lost message)&lt;&#x2F;li&gt;
&lt;li&gt;Process crashes between the two writes → either phantom or lost message, depending on when&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Traditional fixes like two-phase commit, distributed transactions, or ad-hoc retry logic are costly, slow, and still vulnerable to failures. Event sourcing is powerful but requires an architectural commitment that many teams can’t justify.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;strong&gt;Transactional Outbox pattern&lt;&#x2F;strong&gt; elegantly sidesteps the problem.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;why-the-outbox-pattern&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-the-outbox-pattern&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Why the Outbox Pattern&lt;&#x2F;h2&gt;
&lt;p&gt;The pattern works in three phases:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1 - Atomic Transaction (Synchronous):&lt;&#x2F;strong&gt; Save both the business entity AND the outbox event row in the same database transaction. Both writes succeed together or fail together. No partial states.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2 - Relay and Publisher (Asynchronous):&lt;&#x2F;strong&gt; A separate scheduler polls the outbox table for PENDING events and publishes them to the message broker. If the broker is down, events remain PENDING in the database. No loss.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Phase 3 - Message Broker (Asynchronous):&lt;&#x2F;strong&gt; Events are published to topics&#x2F;exchanges. Downstream consumers subscribe and process them idempotently using the event ID (UUID) as a deduplication key.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Key insight:&lt;&#x2F;strong&gt; If the database commit succeeds, the outbox row definitely exists. If it fails, neither the business data nor the event row persist. No inconsistency.&lt;&#x2F;p&gt;
&lt;p&gt;This creates a strong consistency boundary between the database and the message broker, allowing them to maintain ACID guarantees internally while being eventually consistent externally.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;architectural-benefits&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#architectural-benefits&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Architectural Benefits&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Exactly-once semantics (achieved with consumer deduplication):&lt;&#x2F;strong&gt; Combined with consumer idempotency via event ID, you get at-least-once from the pattern plus exactly-once from the consumer.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;No broker coupling:&lt;&#x2F;strong&gt; The service is resilient to broker downtime. Events queue in the database until the broker recovers.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Failure isolation:&lt;&#x2F;strong&gt; Broker issues don’t cascade to the business transaction.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Observability:&lt;&#x2F;strong&gt; Failed events are recorded in the database with retry counts and error messages.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Partition key preservation:&lt;&#x2F;strong&gt; The aggregateId becomes the Kafka partition key, preserving per-entity ordering.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Separation of concerns:&lt;&#x2F;strong&gt; Database writes are decoupled from message broker concerns via the relay mechanism.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Operational simplicity:&lt;&#x2F;strong&gt; No distributed transactions or two-phase commit needed.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;the-outbox-table&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-outbox-table&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
The Outbox Table&lt;&#x2F;h2&gt;
&lt;p&gt;All events live in a single table, &lt;code&gt;outbox_events&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;CREATE&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; TABLE&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; outbox_events&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    id             UUID          &lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;PRIMARY KEY&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt; DEFAULT&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; gen_random_uuid&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    aggregate_type &lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;VARCHAR&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;100&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;  NOT NULL&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    aggregate_id   &lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;VARCHAR&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;255&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;  NOT NULL&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    event_type     &lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;VARCHAR&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;100&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;  NOT NULL&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    payload        &lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;TEXT&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;          NOT NULL&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    status&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;         VARCHAR&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;20&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;   NOT NULL&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt; DEFAULT&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;PENDING&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    retry_count    &lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;INTEGER&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;       NOT NULL&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt; DEFAULT&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    error_message  &lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;TEXT&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    created_at     &lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;TIMESTAMP&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;TZ&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;   NOT NULL&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt; DEFAULT&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; now&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    processed_at   &lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;TIMESTAMP&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;TZ&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;CREATE&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; INDEX&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; idx_outbox_status_created&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ON&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; outbox_events(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;status&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;, created_at);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Column meanings:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;id&lt;&#x2F;code&gt; - Deduplication key for consumers (required for idempotent processing)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;aggregate_type&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;aggregate_id&lt;&#x2F;code&gt; - Business routing (which entity owns this event)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;event_type&lt;&#x2F;code&gt; - Event classification (maps to Kafka topic or RabbitMQ routing key)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;payload&lt;&#x2F;code&gt; - JSON serialization of the domain event&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;status&lt;&#x2F;code&gt; - PENDING (awaiting relay), PROCESSED (successfully delivered), FAILED (exhausted retries)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;retry_count&lt;&#x2F;code&gt; - Number of attempts made&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;error_message&lt;&#x2F;code&gt; - Last failure reason for debugging&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;created_at&lt;&#x2F;code&gt; - When the event was persisted (DB commit time)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;processed_at&lt;&#x2F;code&gt; - When the relay successfully delivered it&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;data-flow-diagram&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#data-flow-diagram&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Data Flow Diagram&lt;&#x2F;h2&gt;
&lt;p&gt;The flow moves through three logical layers:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;mermaid&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;graph TD&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    subgraph Service[&amp;quot;Service Layer (Business Logic)&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        OS[&amp;quot;OrderService.createOrder()&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        TXN[&amp;quot;@Transactional Boundary&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        DBW[&amp;quot;Save Order to DB&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        OUTBOX[&amp;quot;Publish Event to Outbox&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    subgraph Database[&amp;quot;PostgreSQL Database&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ORDERS[&amp;quot;orders table&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        OUTBOXTBL[&amp;quot;outbox_events table&amp;lt;br&#x2F;&amp;gt;(status=PENDING)&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    subgraph Relay[&amp;quot;OutboxEventRelay (@Scheduled)&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        POLL[&amp;quot;Poll: SELECT WHERE status=PENDING&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        SEND[&amp;quot;Broker Adapter.send()&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        UPDATE[&amp;quot;UPDATE status=PROCESSED&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    subgraph Broker[&amp;quot;Kafka or RabbitMQ&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        TOPIC[&amp;quot;Topic&#x2F;Exchange&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    subgraph Consumer[&amp;quot;Downstream Services&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        CS[&amp;quot;Consumer Service&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        DEDUP[&amp;quot;Deduplicate by event.id&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        PROCESS[&amp;quot;Process Event&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    OS --&amp;gt; TXN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    TXN --&amp;gt; DBW&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    DBW --&amp;gt; ORDERS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    TXN --&amp;gt; OUTBOX&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    OUTBOX --&amp;gt; OUTBOXTBL&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    OUTBOXTBL --&amp;gt;|COMMIT| RELAY&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    RELAY --&amp;gt; POLL&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    POLL --&amp;gt;|Every 5s| SEND&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    SEND --&amp;gt; TOPIC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    TOPIC --&amp;gt; CS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    CS --&amp;gt; DEDUP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    DEDUP --&amp;gt; PROCESS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;failure-modes-and-recovery&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#failure-modes-and-recovery&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Failure Modes and Recovery&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Failure&lt;&#x2F;th&gt;&lt;th&gt;Handled By&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;DB commit succeeds, broker publish fails&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Relay retries on next cycle (automatic recovery)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;DB transaction rolls back&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Event row never inserted; no phantom message&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Relay crashes mid-cycle&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Next cycle picks up PENDING events (idempotent)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Broker is down for hours&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Events remain PENDING in DB; relay publishes when broker recovers&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Event permanently malformed&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Marked FAILED after max-retries; operator reviews error_message&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Duplicate delivery&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Consumer must deduplicate using event id (UUID)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Monitor the outbox table for events stuck in PENDING state (age &amp;gt; 5× relay interval) or accumulating in FAILED state. These are early warning signs of broker issues or payload corruption.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;implementation-guide&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#implementation-guide&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Implementation Guide&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Boot 3.x or 4.x&lt;&#x2F;li&gt;
&lt;li&gt;Java 17+&lt;&#x2F;li&gt;
&lt;li&gt;Spring Data JPA with PostgreSQL (or compatible database with UUID support)&lt;&#x2F;li&gt;
&lt;li&gt;Apache Kafka or RabbitMQ&lt;&#x2F;li&gt;
&lt;li&gt;Gradle or Maven&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;Add the starter to build.gradle.kts:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;dependencies &lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    implementation&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;io.github.saumilp.starters:spring-boot-starter-outbox:1.0.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    implementation&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;org.springframework.kafka:spring-kafka:3.3.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; or:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; implementation(&amp;quot;org.springframework.boot:spring-boot-starter-amqp:3.2.0&amp;quot;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Create the Outbox Table&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Run the SQL DDL above against your PostgreSQL database. The table should be created during initial schema setup or via Flyway&#x2F;Liquibase migrations.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Configure the Starter&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In &lt;code&gt;application.yml&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;pring&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;  o&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;utbox&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    e&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;nabled&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language z-boolean&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    r&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;elay-interval-ms&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 5000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    m&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ax-retries&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    b&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ackoff-multiplier&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 2.0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    k&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;afka&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;      b&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ootstrap-servers&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; l&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ocalhost:9092&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;      t&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;opic-prefix&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; o&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;utbox.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Step 3: Implement Your Service&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt; class&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; OrderService&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;    private&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt; final&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; OrderRepository&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; orderRepository&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;    private&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt; final&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; OutboxPublisher&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; outboxPublisher&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Transactional&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;    public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; Order&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; createOrder&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;CreateOrderRequest&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; req&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        Order&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; order&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; orderRepository&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;save&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;new&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; Order&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;req&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;        &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;        outboxPublisher&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;publish&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            new&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; OrderCreatedEvent&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;                order&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;getId&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;                order&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;getCustomerId&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;                order&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;getTotal&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;            )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;        )&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;        &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; order&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;@Transactional&lt;&#x2F;code&gt; annotation ensures both the order AND the outbox event are persisted atomically.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Step 4: Consuming Events&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Consumers must deduplicate by event ID to handle at-least-once delivery:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;Service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt; class&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; OrderEventConsumer&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;    private&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt; final&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; OrderEventRepository&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; eventRepository&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-annotation&quot;&gt;KafkaListener&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;topics&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;outbox.ORDER_CREATED&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;    public&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; void&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; handleOrderCreated&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;OrderCreatedEvent&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; event&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;        &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Check if already processed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;eventRepository&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;existsByEventId&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;event&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;getId&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            return&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;        &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;        &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Process the event&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;        processOrder&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;event&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;        &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;        &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Record that we processed it&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;        eventRepository&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;save&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;new&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; ProcessedEvent&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;event&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;getId&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Consumer Idempotency Patterns:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;In-memory deduplication (low-traffic):&lt;&#x2F;strong&gt; Store event IDs in a HashSet. Simple but lost on restart.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Database deduplication (recommended):&lt;&#x2F;strong&gt; Store processed event IDs in a database table with unique constraint. Survives restarts.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Idempotent business operations (best):&lt;&#x2F;strong&gt; Make the operation itself idempotent. Use event ID as a key. If the operation runs twice, it’s safe.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Database constraints (clever):&lt;&#x2F;strong&gt; Use UNIQUE constraint on (event_id, business_key). The second insert fails safely.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;infrastructure-and-deployment&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#infrastructure-and-deployment&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Infrastructure and Deployment&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Docker Compose for local development:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;v&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ersion&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;3.8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ervices&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;  p&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ostgres&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    i&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;mage&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ostgres:16&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    e&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;nvironment&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;      P&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;OSTGRES_DB&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; o&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;utbox_demo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;      P&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;OSTGRES_PASSWORD&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ostgres&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    p&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;orts&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;      -&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;5432:5432&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    v&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;olumes&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;      -&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ostgres_data:&#x2F;var&#x2F;lib&#x2F;postgresql&#x2F;data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;  k&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;afka&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    i&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;mage&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; c&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;onfluentinc&#x2F;cp-kafka:7.6.0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    d&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;epends_on&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;      -&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; z&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ookeeper&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    p&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;orts&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;      -&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;9092:9092&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    e&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;nvironment&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;      K&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;AFKA_BROKER_ID&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;      K&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;AFKA_ZOOKEEPER_CONNECT&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; z&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ookeeper:2181&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;      K&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;AFKA_ADVERTISED_LISTENERS&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; P&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;LAINTEXT:&#x2F;&#x2F;kafka:29092,PLAINTEXT_HOST:&#x2F;&#x2F;localhost:9092&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;      K&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;AFKA_AUTO_CREATE_TOPICS_ENABLE&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;  z&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ookeeper&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    i&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;mage&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; c&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;onfluentinc&#x2F;cp-zookeeper:7.6.0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    p&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;orts&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;      -&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;2181:2181&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    e&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;nvironment&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;      Z&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;OOKEEPER_CLIENT_PORT&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 2181&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;v&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;olumes&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;  p&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ostgres_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Testing the flow:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Start services: &lt;code&gt;docker-compose up&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Create an order: &lt;code&gt;curl -X POST http:&#x2F;&#x2F;localhost:8080&#x2F;orders -H &quot;Content-Type: application&#x2F;json&quot; -d &#x27;{&quot;customerId&quot;:&quot;cust-123&quot;, &quot;amount&quot;:99.99}&#x27;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Check outbox table: &lt;code&gt;SELECT * FROM outbox_events;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Verify Kafka topic has the event: &lt;code&gt;kafka-console-consumer.sh --topic outbox.ORDER_CREATED --bootstrap-servers localhost:9092&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Verify consumer processed it: &lt;code&gt;SELECT * FROM processed_events;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;strong&gt;CI&#x2F;CD Pipeline (GitHub Actions):&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ame&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; T&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;est Outbox Starter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-language z-boolean&quot;&gt;on&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ush&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ull_request&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;j&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;obs&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;  t&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;est&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    r&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;uns-on&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; u&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;buntu-latest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    s&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ervices&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;      p&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ostgres&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;        i&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;mage&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ostgres:16&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;        e&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;nv&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;          P&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;OSTGRES_PASSWORD&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ostgres&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;        o&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ptions&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier&quot;&gt;-&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;          --health-cmd pg_isready&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;          --health-interval 10s&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;          --health-timeout 5s&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;          --health-retries 5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;    s&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;teps&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;      -&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt; u&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ses&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ctions&#x2F;checkout@v3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;      -&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt; u&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ses&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ctions&#x2F;setup-java@v3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;        w&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ith&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;          j&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ava-version&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 21&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;          d&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;istribution&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;emurin&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;      -&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt; r&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;un&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; .&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;gradlew test --info&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;key-takeaways&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#key-takeaways&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Key Takeaways&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;For Architects:&lt;&#x2F;strong&gt;
The Outbox pattern is NOT a substitute for event sourcing. Use it for reliable event publishing in transaction-based services. For temporal queries, event replay, or audit trails, event sourcing is the better choice.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;For Developers:&lt;&#x2F;strong&gt;
Remember: the pattern provides at-least-once delivery, so consumers MUST be idempotent. Use the event ID as your deduplication key. Test both success and failure paths (relay down, broker down, transaction rollback).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;For DevOps:&lt;&#x2F;strong&gt;
Monitor the outbox table cardinality and relay latency. Set up alerts for FAILED events accumulating. The relay is stateless, so you can run multiple instances for redundancy.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Best Practices:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Always use event IDs for consumer deduplication&lt;&#x2F;li&gt;
&lt;li&gt;Make business operations idempotent, not just event deduplication&lt;&#x2F;li&gt;
&lt;li&gt;Set reasonable max-retries (usually 3-5)&lt;&#x2F;li&gt;
&lt;li&gt;Use exponential backoff for retries&lt;&#x2F;li&gt;
&lt;li&gt;Log failed events with full error messages&lt;&#x2F;li&gt;
&lt;li&gt;Partition by aggregateId for ordering guarantees&lt;&#x2F;li&gt;
&lt;li&gt;Never delete from outbox table; archive to cold storage after 30 days&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;Kafka vs RabbitMQ Trade-offs:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Aspect&lt;&#x2F;th&gt;&lt;th&gt;Kafka&lt;&#x2F;th&gt;&lt;th&gt;RabbitMQ&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Ordering&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Per-partition (use aggregateId)&lt;&#x2F;td&gt;&lt;td&gt;Per-queue (declare exclusive queues)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;At-least-once&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Built-in with consumer groups&lt;&#x2F;td&gt;&lt;td&gt;Requires manual ACKs&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Retention&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Days&#x2F;weeks (configurable)&lt;&#x2F;td&gt;&lt;td&gt;Until consumed&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Scaling&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Horizontal (partitions)&lt;&#x2F;td&gt;&lt;td&gt;Vertical plus clustering&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Complexity&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Moderate (Zookeeper&#x2F;KRaft)&lt;&#x2F;td&gt;&lt;td&gt;Simple setup&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;For the Outbox pattern, Kafka’s partition-based ordering is ideal. For RabbitMQ, use topic exchanges with routing keys bound to dedicated queues.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;references-and-further-reading&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#references-and-further-reading&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
References and Further Reading&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transactional Outbox Pattern:&lt;&#x2F;strong&gt; https:&#x2F;&#x2F;microservices.io&#x2F;patterns&#x2F;data&#x2F;transactional-outbox.html&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Spring Boot Starter Outbox:&lt;&#x2F;strong&gt; https:&#x2F;&#x2F;github.com&#x2F;saumilpatel&#x2F;spring-boot-starter-outbox&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Event-Driven Architecture:&lt;&#x2F;strong&gt; https:&#x2F;&#x2F;www.oreilly.com&#x2F;library&#x2F;view&#x2F;building-event-driven-microservices&#x2F;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Kafka at-least-once semantics:&lt;&#x2F;strong&gt; https:&#x2F;&#x2F;kafka.apache.org&#x2F;documentation&#x2F;#consumerconfigs_enable.auto.commit&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Spring Data JPA:&lt;&#x2F;strong&gt; https:&#x2F;&#x2F;spring.io&#x2F;projects&#x2F;spring-data-jpa&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL UUID type:&lt;&#x2F;strong&gt; https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;datatype-uuid.html&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;troubleshooting&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#troubleshooting&quot; aria-label=&quot;Permalink to this section&quot; data-pagefind-ignore&gt;#&lt;&#x2F;a&gt;
Troubleshooting&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Q: Events are stuck in PENDING status&lt;&#x2F;strong&gt;
A: Check if the relay is running (logs should show “Polling outbox…”). Verify broker connectivity. Check error_message column for details.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Q: “Transaction already rolled back” error when calling publish()&lt;&#x2F;strong&gt;
A: OutboxPublisher must be called within a @Transactional method. The transaction must be active and not already marked for rollback.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Q: Duplicate messages in the broker&lt;&#x2F;strong&gt;
A: The pattern guarantees at-least-once. Consumers MUST deduplicate by event ID. This is not a bug; it’s the contract.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Q: Relay is taking too long (&amp;gt; relay-interval-ms)&lt;&#x2F;strong&gt;
A: Increase relay-interval-ms or add database indexes. If outbox_events table is huge, partition it by created_at or status.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Q: How do I handle event schema changes?&lt;&#x2F;strong&gt;
A: Version your event payloads (add a “version” field). Consumers should support multiple versions. Use JSON Schema or Protocol Buffers for validation.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Q: Can I use this with RabbitMQ?&lt;&#x2F;strong&gt;
A: Yes. Configure rabbitmq section instead of kafka in application.yml. The pattern is broker-agnostic.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Colophon: how this site is built</title>
        <published>2026-06-28T00:00:00+00:00</published>
        <updated>2026-06-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/notes/site-colophon/"/>
        <id>https://saumilp.dev/notes/site-colophon/</id>
        
        <content type="html" xml:base="https://saumilp.dev/notes/site-colophon/">&lt;p&gt;This site is a static, dependency-light technical publication.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Generator:&lt;&#x2F;strong&gt; &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.getzola.org&quot;&gt;Zola&lt;&#x2F;a&gt; — a single fast Rust binary, no Node build pipeline.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Templates:&lt;&#x2F;strong&gt; custom &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;keats.github.io&#x2F;tera&#x2F;&quot;&gt;Tera&lt;&#x2F;a&gt; partials and macros.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Styles:&lt;&#x2F;strong&gt; a hand-written SCSS design system compiled by Zola, driven by CSS custom properties (light + dark).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Search:&lt;&#x2F;strong&gt; &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pagefind.app&quot;&gt;Pagefind&lt;&#x2F;a&gt; — a fully static search index generated after the build.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Interactions:&lt;&#x2F;strong&gt; a few tiny vanilla-JS modules (theme toggle, copy-code, mobile nav, search, TOC) — no framework.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hosting:&lt;&#x2F;strong&gt; GitHub Pages, built and deployed by GitHub Actions.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;No client-side framework, no CDN dependency, no external analytics.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Java Review MCP Server</title>
        <published>2025-02-01T00:00:00+00:00</published>
        <updated>2025-02-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/projects/java-review-mcp/"/>
        <id>https://saumilp.dev/projects/java-review-mcp/</id>
        
        <content type="html" xml:base="https://saumilp.dev/projects/java-review-mcp/">&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;AI-assisted code review requires understanding both the syntax and the broader system design context. Generic code review tools miss enterprise-specific concerns: transaction boundaries, observability instrumentation, context propagation patterns, and domain-driven design principles. Integrating code review into a developer’s workflow without context switching is difficult.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;&#x2F;h2&gt;
&lt;p&gt;Built an MCP (Model Context Protocol) server that exposes a Java project’s structure and analysis capabilities to Claude, enabling context-aware code reviews. The server parses the codebase, builds an AST-based understanding of packages, classes, and dependencies, then provides Claude with structured access to perform deep reviews.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Key capabilities:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Project structure analysis and dependency graph visualization&lt;&#x2F;li&gt;
&lt;li&gt;Class and method-level code analysis with architectural context&lt;&#x2F;li&gt;
&lt;li&gt;Transaction boundary detection and verification&lt;&#x2F;li&gt;
&lt;li&gt;Observability instrumentation pattern recognition&lt;&#x2F;li&gt;
&lt;li&gt;Security vulnerability pattern matching&lt;&#x2F;li&gt;
&lt;li&gt;Architecture constraint validation&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Full project analysis over isolated review:&lt;&#x2F;strong&gt; Slower startup but enables holistic review feedback. Caches analysis to reduce latency on repeated reviews.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Claude as reviewer over specialized linters:&lt;&#x2F;strong&gt; More context-aware and human-friendly feedback, but requires language model access and costs per review.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;MCP protocol over custom API:&lt;&#x2F;strong&gt; Ensures interoperability with Claude and other tools, but adds protocol overhead.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;outcome&quot;&gt;Outcome&lt;&#x2F;h2&gt;
&lt;p&gt;Seamless integration of AI-assisted code review into Claude workflows. Teams get sophisticated architectural review feedback without leaving their editor or IDE. Particularly valuable for enforcing enterprise patterns across distributed teams and mentoring junior engineers on system design principles.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Dev machine setup for Flutter App development</title>
        <published>2024-11-16T00:00:00+00:00</published>
        <updated>2024-11-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/blog/dev-machine-setup-for-flutter/"/>
        <id>https://saumilp.dev/blog/dev-machine-setup-for-flutter/</id>
        
        <summary type="html">&lt;p&gt;&lt;img src=&quot;&#x2F;img&#x2F;2024&#x2F;dev-machine-setup-flutter-osx&#x2F;macOs-dev-machine-setup.webp&quot; alt=&quot;Flutter on macOS&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Setting up &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;flutter.dev&#x2F;&quot;&gt;Flutter&lt;&#x2F;a&gt; on macOS can be staightforward process when using &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;brew.sh&#x2F;&quot;&gt;Homebrew&lt;&#x2F;a&gt;, a popular package manager for macOS. Flutter, a powerful open-source UI software development toolkit by Google, enables developers to build natively compiled mobile, web, and desktop applications from a single codebase. In this guide, we’ll walk you through the necessary steps to install and configure Flutter on macOS system using Homebrew. Additionally, we will cover the installation of essential tools like Android Studio and Xcode, update Ruby, and set up CocoaPods to ensure a smooth development experience.</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Rust Learning Lab</title>
        <published>2024-06-15T00:00:00+00:00</published>
        <updated>2024-06-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/projects/rust-learning-lab/"/>
        <id>https://saumilp.dev/projects/rust-learning-lab/</id>
        
        <content type="html" xml:base="https://saumilp.dev/projects/rust-learning-lab/">&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;Learning Rust requires understanding both the language syntax and the systems programming concepts it enables. Most tutorials focus on syntax, while real-world systems programming involves memory safety, concurrency patterns, and performance optimization. There’s a gap between “Hello, World!” and building production-grade systems in Rust.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;&#x2F;h2&gt;
&lt;p&gt;Created a comprehensive learning project that progresses from fundamentals to advanced systems programming. The lab covers ownership and borrowing through practical examples, explores concurrency patterns with real-world scenarios, and includes deep dives into performance optimization.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Key areas:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Core concepts:&lt;&#x2F;strong&gt; Ownership, borrowing, lifetimes, and the type system with runnable examples&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Systems programming:&lt;&#x2F;strong&gt; Memory management, unsafe code patterns, and performance profiling&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Concurrency:&lt;&#x2F;strong&gt; Threading, async&#x2F;await, channels, and lock-free patterns&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Algorithms &amp;amp; data structures:&lt;&#x2F;strong&gt; Implementations with trade-off analysis&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Performance:&lt;&#x2F;strong&gt; Benchmarking, profiling, and optimization techniques&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Breadth over depth:&lt;&#x2F;strong&gt; Covers many topics at intermediate level rather than mastering one deeply. Designed as a learning progression, not a reference.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Examples over libraries:&lt;&#x2F;strong&gt; Most implementations are from scratch to teach concepts, not production-ready code.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Educational comments over minimal code:&lt;&#x2F;strong&gt; Heavy documentation and explanations to aid learning, which would be excessive in production code.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;outcome&quot;&gt;Outcome&lt;&#x2F;h2&gt;
&lt;p&gt;A self-directed learning resource for engineers transitioning to Rust or deepening their systems programming knowledge. Particularly valuable for those coming from higher-level languages who need to understand memory safety and performance characteristics. Serves as both a learning guide and a reference for Rust patterns in real systems.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Learning to fly with Rust</title>
        <published>2024-04-01T00:00:00+00:00</published>
        <updated>2024-04-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/blog/app-on-flyio/"/>
        <id>https://saumilp.dev/blog/app-on-flyio/</id>
        
        <summary type="html">&lt;p&gt;&lt;img src=&quot;&#x2F;img&#x2F;2024&#x2F;app-on-flyio&#x2F;rust_in_hotair_balloon.webp&quot; alt=&quot;Rust in Flyio&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Recently I updated my blog-site with &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.getzola.org&#x2F;&quot;&gt;&lt;strong&gt;Zola&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;, and then I started thinking to host Rust application on cloud providers. There are multiple CSPs ( &lt;em&gt;Cloud Service Providers&lt;&#x2F;em&gt; ) enabling Rust enthusiasts Developers (also known as “Rustaceans”) to host web-apps on cloud.</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Zola quickstart</title>
        <published>2024-03-19T00:00:00+00:00</published>
        <updated>2024-03-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/blog/intro-to-zola-ssg/"/>
        <id>https://saumilp.dev/blog/intro-to-zola-ssg/</id>
        
        <summary type="html">&lt;p&gt;This post is one of the first one after a long time, I am evaluating &lt;strong&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.getzola.org&#x2F;&quot;&gt;&lt;code&gt;Zola&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; as replacement of Jekyll, and thought of creating a blog for step-by-step site building.&lt;&#x2F;p&gt;
&lt;p&gt;Whether you like Rust and SSG or you do not know any of these, if you wish to create a website or a blog, &lt;code&gt;Zola&lt;&#x2F;code&gt; is made for you! We are going to discover here the basic concepts.&amp;hellip;
&lt;&#x2F;p&gt;
</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Mastodon Toot Client</title>
        <published>2024-02-01T00:00:00+00:00</published>
        <updated>2024-02-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/projects/mastodon-toot-client/"/>
        <id>https://saumilp.dev/projects/mastodon-toot-client/</id>
        
        <content type="html" xml:base="https://saumilp.dev/projects/mastodon-toot-client/">&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;Mastodon’s official apps don’t support scheduling or file-based content. Automating posts for a personal account — say, a weekly architecture tip from a text file — required either a Python script with fragile state management or a paid third-party service.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;&#x2F;h2&gt;
&lt;p&gt;Built a stateless Rust CLI: reads a text file of toots (one per line), posts the next unposted one to the Mastodon API, and marks it done by moving the line to a &lt;code&gt;done&lt;&#x2F;code&gt; file. Runs as a GitHub Actions cron job — no server required, credentials stored as Actions secrets.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stateless file-append over a database:&lt;&#x2F;strong&gt; Simpler deployment and easier to audit. The “done” file doubles as a history log.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Rust over Python:&lt;&#x2F;strong&gt; Compile-time guarantees matter when the binary runs unattended. The async Tokio runtime handles the HTTP call without blocking.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;outcome&quot;&gt;Outcome&lt;&#x2F;h2&gt;
&lt;p&gt;A zero-dependency automation that posts on a schedule without any hosted infrastructure. The approach generalises to any API-driven content automation with similar stateless patterns.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Spring Boot Starters</title>
        <published>2024-01-15T00:00:00+00:00</published>
        <updated>2024-01-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/projects/spring-boot-starters/"/>
        <id>https://saumilp.dev/projects/spring-boot-starters/</id>
        
        <content type="html" xml:base="https://saumilp.dev/projects/spring-boot-starters/">&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;Building microservices requires solving the same problems repeatedly: atomic event publishing, context propagation across async boundaries, cardinality control in observability pipelines, and idempotent message consumption. Each team implements these differently, leading to fragmented traces, inconsistent retry logic, and high-cardinality metrics explosions.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;&#x2F;h2&gt;
&lt;p&gt;Created a suite of focused Spring Boot starters that encode opinions about how to solve these problems consistently across a microservice mesh. Each starter is independently deployable and composable, allowing teams to adopt one, several, or all without vendor lock-in.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Key starters:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;spring-boot-starter-outbox:&lt;&#x2F;strong&gt; Transactional outbox pattern for reliable event publishing with automatic relay and retry logic&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;spring-boot-starter-observability:&lt;&#x2F;strong&gt; Enforced context propagation, cardinality controls, and extension points for custom instrumentation&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;spring-boot-starter-idempotency:&lt;&#x2F;strong&gt; Consumer-side deduplication with pluggable backends (in-memory, Redis, database)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Auto-configuration over manual setup:&lt;&#x2F;strong&gt; Faster onboarding but requires trust in the starter’s opinions. Overrideable via &lt;code&gt;@ConditionalOnMissingBean&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Outbox relay over broker transactions:&lt;&#x2F;strong&gt; Avoids distributed transaction complexity but introduces a scheduled component that must be monitored.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Platform-level observability over per-team instrumentation:&lt;&#x2F;strong&gt; Ensures consistency but loses flexibility for teams with unique requirements.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;outcome&quot;&gt;Outcome&lt;&#x2F;h2&gt;
&lt;p&gt;A production-proven library reducing time-to-reliability for event-driven systems. Eliminates boilerplate around event publishing, context propagation, and idempotent processing. Used across fintech and telecom microservice architectures handling millions of events per day.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>draw.io Architecture Libraries</title>
        <published>2024-01-01T00:00:00+00:00</published>
        <updated>2024-01-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/projects/draw-io-architecture-libraries/"/>
        <id>https://saumilp.dev/projects/draw-io-architecture-libraries/</id>
        
        <content type="html" xml:base="https://saumilp.dev/projects/draw-io-architecture-libraries/">&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;Architecture diagrams in most teams suffer from inconsistency — each engineer invents their own notation, colour scheme, and level of abstraction. Reviews spend more time decoding the diagram than discussing the decision.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;&#x2F;h2&gt;
&lt;p&gt;Built a curated set of draw.io shape libraries covering C4 model levels (Context, Container, Component), AWS service icons, and a custom notation layer for domain events and async flows. Libraries are XML files that can be imported directly into draw.io desktop or app.diagrams.net with one click.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Chose draw.io over Mermaid&#x2F;PlantUML:&lt;&#x2F;strong&gt; Draw.io diagrams are visual-first and accessible to non-engineers. Mermaid is better for version control but harder to layout precisely.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Static XML over dynamic generation:&lt;&#x2F;strong&gt; Keeps the library dependency-free and usable offline. The trade-off is that updating icons requires editing XML by hand.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;outcome&quot;&gt;Outcome&lt;&#x2F;h2&gt;
&lt;p&gt;The libraries are used in personal architecture documentation and have been shared with colleagues for review. The C4 layer in particular cuts diagram preparation time significantly by providing pre-built component shapes with consistent colour coding.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Enterprise Spring Patterns</title>
        <published>2023-09-01T00:00:00+00:00</published>
        <updated>2023-09-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/projects/enterprise-spring-patterns/"/>
        <id>https://saumilp.dev/projects/enterprise-spring-patterns/</id>
        
        <content type="html" xml:base="https://saumilp.dev/projects/enterprise-spring-patterns/">&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;Spring Boot makes the happy path easy, but enterprise systems have edge cases: transactional outbox patterns, idempotent consumers, domain event publishing, and layered service boundaries. These topics are scattered across blog posts with inconsistent advice.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;&#x2F;h2&gt;
&lt;p&gt;Built a reference application demonstrating enterprise patterns with real infrastructure: PostgreSQL for persistence, Kafka for event streaming, and Redis for idempotency keys. Each pattern is isolated in its own package with an integration test that uses Testcontainers.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Testcontainers over H2:&lt;&#x2F;strong&gt; H2 in-memory DB hides PostgreSQL-specific behaviour. Testcontainers is slower to start but catches real issues.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Outbox pattern over direct Kafka publish:&lt;&#x2F;strong&gt; Ensures at-least-once delivery without distributed transactions.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;outcome&quot;&gt;Outcome&lt;&#x2F;h2&gt;
&lt;p&gt;A self-contained reference for backend engineers migrating from monolith Spring MVC to event-driven architectures. Covers the gap between “Hello World” Spring Boot tutorials and production-grade system design.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Java Design Patterns</title>
        <published>2023-06-01T00:00:00+00:00</published>
        <updated>2023-06-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/projects/java-design-patterns/"/>
        <id>https://saumilp.dev/projects/java-design-patterns/</id>
        
        <content type="html" xml:base="https://saumilp.dev/projects/java-design-patterns/">&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;Design patterns are often taught with UML diagrams and abstract descriptions. Real engineers need runnable code they can step through, modify, and connect to frameworks they already use (Spring, CDI, etc.).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;&#x2F;h2&gt;
&lt;p&gt;Each pattern gets its own Maven module: a minimal implementation, a Spring Boot integration where it makes sense, and JUnit 5 tests that assert the expected behaviour. Patterns are documented with a short “when to use &#x2F; when not to” note rather than a textbook definition.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Maven multi-module over Gradle:&lt;&#x2F;strong&gt; Heavier to set up but more familiar to enterprise Java developers who are the primary audience.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Spring integration optional:&lt;&#x2F;strong&gt; Not every pattern maps naturally to Spring — forced integration would produce misleading examples.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;outcome&quot;&gt;Outcome&lt;&#x2F;h2&gt;
&lt;p&gt;A living reference that maps GoF patterns to real Java 21 idioms (records, sealed classes, switch expressions) and framework-level equivalents in Spring Boot 3.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>gh-stats</title>
        <published>2023-03-01T00:00:00+00:00</published>
        <updated>2023-03-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/projects/gh-stats/"/>
        <id>https://saumilp.dev/projects/gh-stats/</id>
        
        <content type="html" xml:base="https://saumilp.dev/projects/gh-stats/">&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;GitHub contribution graphs and repository statistics are useful for portfolios and dashboards, but embedding them requires either heavy JavaScript libraries or manual updates. Serverless functions are perfect for this — lightweight, scalable, and stateless — but most implementations over-engineer the solution with unnecessary dependencies.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;&#x2F;h2&gt;
&lt;p&gt;Built a minimal serverless function that queries the GitHub API and generates clean SVG visualizations. The service is stateless, dependency-light, and optimized for fast response times. Each visualization is generated on-demand, eliminating cache invalidation headaches.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Key capabilities:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Contribution graph:&lt;&#x2F;strong&gt; Visual timeline of GitHub contributions&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Repository stats:&lt;&#x2F;strong&gt; Stars, forks, language distribution&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;User stats:&lt;&#x2F;strong&gt; Public profile metrics and activity summaries&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;SVG generation:&lt;&#x2F;strong&gt; No external rendering services — pure SVG generation&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Caching headers:&lt;&#x2F;strong&gt; Appropriate cache directives for dashboard embedding&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SVG over canvas&#x2F;images:&lt;&#x2F;strong&gt; SVG is lightweight and infinitely scalable, but rendering is limited to basic shapes and text. No photorealistic charts.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;On-demand over pre-computed:&lt;&#x2F;strong&gt; Function calls add latency, but eliminate stale data and infrastructure complexity.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Minimal dependencies:&lt;&#x2F;strong&gt; Reduces bundle size and attack surface, but requires more custom logic than using charting libraries.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;outcome&quot;&gt;Outcome&lt;&#x2F;h2&gt;
&lt;p&gt;A lightweight tool for developers who want GitHub stats on their websites, portfolios, or dashboards without heavy client-side libraries. Particularly useful for status pages, portfolio sites, and internal dashboards where fresh data matters but heavy infrastructure doesn’t.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Automatic ID generation in Apache Solr</title>
        <published>2015-02-16T00:00:00+00:00</published>
        <updated>2015-02-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/blog/unique-id-generation-in-solr/"/>
        <id>https://saumilp.dev/blog/unique-id-generation-in-solr/</id>
        
        <summary type="html">&lt;p&gt;I have been working on Apache Solr for last few months, and have been recieving requirements to speed up query process. As part of the investigation, i found out as retrieved documents’ unique id generation contributes query processing.And hence i have decided to add this post.&amp;hellip;
&lt;&#x2F;p&gt;
</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>XML Schema Design Patterns</title>
        <published>2015-02-09T00:00:00+00:00</published>
        <updated>2015-02-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/blog/xml-schema-design-patterns/"/>
        <id>https://saumilp.dev/blog/xml-schema-design-patterns/</id>
        
        <summary type="html">&lt;p&gt;This might be one of the old topic - specially when lot of developers are working with RESTful Services, but i would like to point out couple of pros and cons.&lt;&#x2F;p&gt;
&lt;p&gt;As most of the developers know, XML schema is very powerful, but they lack object-orientation, and are intended to capture a data model rather than an object model. In last one and half decades, there has been few tools available to architects and developers to be able to make use off some of the object orientation principles - like polymorphism for flexibility. But ultimately the whole point of the services must be general enough that it can communicate across languages. Hence i have attempted to put together some information around some of the very well known Design patterns.&amp;hellip;
&lt;&#x2F;p&gt;
</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Curried Function in Java</title>
        <published>2015-01-31T00:00:00+00:00</published>
        <updated>2015-01-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/blog/currying-function-in-java/"/>
        <id>https://saumilp.dev/blog/currying-function-in-java/</id>
        
        <summary type="html">&lt;p&gt;&lt;strong&gt;Currying&lt;&#x2F;strong&gt; is a technique of transforming a function with multiple arguments into a function with just single argument. The single argument is the value of the first argument from the original function and it returns another single argument function. This in turn would take the second original argument and itself return another single argument function. This kind of chaining continues over the arguments of the original. The last in the chain will have access to all the arguments and so can do whatever it needs to do.&amp;hellip;
&lt;&#x2F;p&gt;
</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Quick Apache Solr Setup</title>
        <published>2015-01-31T00:00:00+00:00</published>
        <updated>2015-01-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/blog/quick-solr-setup/"/>
        <id>https://saumilp.dev/blog/quick-solr-setup/</id>
        
        <summary type="html">&lt;p&gt;&lt;img src=&quot;&#x2F;img&#x2F;2015&#x2F;quick-solr-setup&#x2F;solr-header-image.webp&quot; alt=&quot;Solr&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It has been long time since I started with setup&#x2F;configuration related blog post. This post provides steps for setting up developer machine for &lt;strong&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solr.apache.org&#x2F;&quot;&gt;Apache Solr&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt;. Apache Solr is an opensource search engine written in Java, from the Apache Lucene project - with many powerful &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;lucene.apache.org&#x2F;solr&#x2F;features.html&quot;&gt;features&lt;&#x2F;a&gt;.</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Closures in Java</title>
        <published>2015-01-23T00:00:00+00:00</published>
        <updated>2015-01-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Saumil Patel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://saumilp.dev/blog/closures-in-java/"/>
        <id>https://saumilp.dev/blog/closures-in-java/</id>
        
        <summary type="html">&lt;p&gt;Working on last few projects, i’ve realized that use of closures can come really handy in day-to-day work.
Can you imagine writing loops after loops to extract or collect properties from list of custom objects? It can be tiring and painful. And hence, I have decided to add this post - mainly to point out couple of situations where introduction to closures can really ease this pain.&amp;hellip;
&lt;&#x2F;p&gt;
</summary>
        
    </entry>
</feed>
