<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Marcelo Domingues on Medium]]></title>
        <description><![CDATA[Stories by Marcelo Domingues on Medium]]></description>
        <link>https://medium.com/@marcelogdomingues?source=rss-f417e4e45a5e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*A3cvcsbsDdbfmwqgXaQHoA.jpeg</url>
            <title>Stories by Marcelo Domingues on Medium</title>
            <link>https://medium.com/@marcelogdomingues?source=rss-f417e4e45a5e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 04 Nov 2026 22:50:40 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@marcelogdomingues/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[How Hash Maps Work ️]]></title>
            <link>https://medium.com/the-60-second-programmer/how-hash-maps-work-%EF%B8%8F-17e9eabfd9bc?source=rss-f417e4e45a5e------2</link>
            <guid isPermaLink="false">https://medium.com/p/17e9eabfd9bc</guid>
            <category><![CDATA[hashmap]]></category>
            <category><![CDATA[computer-science]]></category>
            <category><![CDATA[dictionary]]></category>
            <category><![CDATA[data-structures]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[Marcelo Domingues]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 09:26:01 GMT</pubDate>
            <atom:updated>2026-11-04T09:26:01.718Z</atom:updated>
            <content:encoded><![CDATA[<h4>Why a dictionary can find a key in roughly one step.</h4><p>A hash map (dict, hash table) stores key-value pairs and finds any key in roughly <strong>O(1)</strong> time. The trick is turning the key into an array index.</p><h3>The core idea</h3><p>1. A <strong>hash function</strong> converts your key into a number.<br>2. That number maps to a <strong>bucket</strong> (an index in an internal array).<br>3. The value gets stored in that bucket — so lookup goes straight there.</p><pre>def bucket_index(key, size):<br>    return hash(key) % size   # turn key into an array index</pre><pre># Conceptually:<br>buckets = [[] for _ in range(8)]<br>def put(key, value):<br>    buckets[bucket_index(key, 8)].append((key, value))</pre><h3>Collisions</h3><p>Two keys can land in the same bucket. Maps handle this by <strong>chaining</strong> (storing a small list per bucket) or <strong>open addressing</strong> (probing for the next free slot). With a good hash function, collisions stay rare and lookups stay fast.</p><h3>When it slows down</h3><p>If too many keys crowd into too few buckets, lookups degrade toward <strong>O(n)</strong>. Real implementations <strong>resize</strong> (rehash into a bigger array) once they get too full, keeping operations near constant time on average.</p><p><strong>TL;DR:</strong> A hash map turns keys into array indices via a hash function, giving average O(1) lookup — collisions and resizing are the details that keep it honest.</p><p>Reach for a dict whenever you find yourself scanning a list to find one item.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=17e9eabfd9bc" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-60-second-programmer/how-hash-maps-work-%EF%B8%8F-17e9eabfd9bc">How Hash Maps Work 🗺️</a> was originally published in <a href="https://medium.com/the-60-second-programmer">The 60-Second Programmer</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Regular Expressions Finally Explained (You’ll Actually Remember This One)]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@marcelogdomingues/regular-expressions-finally-explained-youll-actually-remember-this-one-4384a0a1e6c2?source=rss-f417e4e45a5e------2"><img src="https://cdn-images-1.medium.com/max/1221/1*cayBIImyML_V4mxSL4GV7A.png" width="1221"></a></p><p class="medium-feed-snippet">Regex isn&#x2019;t memorization &#x2014; it&#x2019;s a tiny language with about a dozen pieces, and you can learn all of them today.</p><p class="medium-feed-link"><a href="https://medium.com/@marcelogdomingues/regular-expressions-finally-explained-youll-actually-remember-this-one-4384a0a1e6c2?source=rss-f417e4e45a5e------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@marcelogdomingues/regular-expressions-finally-explained-youll-actually-remember-this-one-4384a0a1e6c2?source=rss-f417e4e45a5e------2</link>
            <guid isPermaLink="false">https://medium.com/p/4384a0a1e6c2</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[developer-tools]]></category>
            <category><![CDATA[regex]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Marcelo Domingues]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 02:21:02 GMT</pubDate>
            <atom:updated>2026-11-04T02:21:02.469Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[The Saga Pattern: Managing Distributed Transactions Across MicroservicesYou split a monolith into…]]></title>
            <link>https://medium.com/devdomain/the-saga-pattern-managing-distributed-transactions-across-microservicesyou-split-a-monolith-into-ce910ef5beb6?source=rss-f417e4e45a5e------2</link>
            <guid isPermaLink="false">https://medium.com/p/ce910ef5beb6</guid>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[Marcelo Domingues]]></dc:creator>
            <pubDate>Tue, 03 Nov 2026 20:11:01 GMT</pubDate>
            <atom:updated>2026-11-03T20:11:01.606Z</atom:updated>
            <content:encoded><![CDATA[<p>The Saga Pattern: Managing Distributed Transactions Across MicroservicesYou split a monolith into services. Orders, payments, and inventory each own their own database. Then the first hard requirement lands: “placing an order must charge the customer and reserve stock — all or nothing.” In a monolith that was one @Transactional method and a single database commit. Across three services with three databases, that single commit no longer exists. The saga pattern is how we get the <em>business</em> guarantee back without pretending the <em>technical</em> guarantee is still there.</p><p>This article explains why the obvious solution (two-phase commit) fails at scale, the two saga styles, how compensation actually works, the failure-handling vocabulary you need (semantic locks, retriable vs compensatable steps, isolation anomalies), and concrete Spring Boot 3.x code for both choreography and orchestration.</p><h3>Why two-phase commit fails at scale</h3><p>Two-phase commit (2PC) is the textbook answer to distributed atomicity. A coordinator asks every participant to <em>prepare</em> (phase 1); if all vote yes, it tells them to <em>commit</em> (phase 2). It genuinely gives you ACID across nodes. It also has properties that make it a poor fit for internet-scale microservices.</p><ul><li><strong>Synchronous coupling and blocking.</strong> Between prepare and commit, each participant holds locks on the affected rows. Every service in the transaction must be up, reachable, and fast <em>at the same time</em>. The transaction’s availability becomes the product of every participant’s availability — the opposite of why you split into services.</li><li><strong>The coordinator is a failure point.</strong> If the coordinator crashes after participants vote “yes” but before it broadcasts the decision, participants are stuck holding locks, unable to safely commit or abort. This is the classic 2PC blocking problem. Recovery requires a durable coordinator log and careful timeout handling.</li><li><strong>It does not scale with latency.</strong> Locks held for the duration of a network round trip kill throughput under contention. A hot row (a popular SKU’s stock count) becomes a global serialization point.</li><li><strong>Heterogeneity.</strong> 2PC needs an XA-capable resource manager on every participant. Your payment provider is an HTTP API. Kafka is not an XA resource in any practical sense. A third-party shipping service will never enlist in your transaction. The moment one participant cannot speak XA, 2PC is off the table.</li></ul><p>The deeper issue is philosophical. Microservices buy you <em>autonomy</em> — independent deployment, independent scaling, independent failure. 2PC reintroduces a tight temporal coupling that throws that autonomy away. Sagas make the opposite trade: they give up immediate, global isolation in exchange for availability and loose coupling.</p><h3>What a saga actually is</h3><p>A saga is a sequence of <strong>local</strong> transactions. Each local transaction updates a single service’s database and publishes a message (an event or a command) that triggers the next local transaction. There is no global lock and no global commit. Each step commits independently and immediately.</p><p>Because there is no rollback across services, a saga handles failure differently from a database. If step 4 fails, you cannot “roll back” steps 1–3 — they already committed. Instead you run <strong>compensating transactions</strong>: new local transactions that semantically undo the effects of the earlier committed steps, in reverse order.</p><p>So a saga has two flavors of step:</p><ul><li><strong>Forward (compensatable) transactions</strong> — T1, T2, ..., Tn — the happy path.</li><li><strong>Compensating transactions</strong> — C1, C2, ..., Cn-1 — the undo path. Ci semantically reverses Ti.</li></ul><p>The guarantee a saga gives you is: <strong>either all forward transactions complete, or a subset of forward transactions complete and the corresponding compensations run.</strong> That is <em>atomicity</em>, but not <em>isolation</em>. We will come back to the isolation hole, because it is where most people get hurt.</p><h3>The worked example: Order → Payment → Inventory</h3><p>Throughout, we use a single scenario. A customer places an order. The system must:</p><ol><li>Create the order in PENDING state (Order Service).</li><li>Charge the customer (Payment Service).</li><li>Reserve stock for the line items (Inventory Service).</li><li>Mark the order CONFIRMED (Order Service).</li></ol><p>If payment fails, we cancel the order. If inventory reservation fails, we <em>refund</em> the payment and cancel the order. Here is the flow with its compensation arrows.</p><pre>Happy path (forward transactions)<br>   ───────────────────────────────────────────────►<br> ┌─────────┐   ┌─────────┐   ┌───────────┐   ┌─────────┐<br> │ ORDER   │   │ PAYMENT │   │ INVENTORY │   │ ORDER   │<br> │ create  │──►│ charge  │──►│ reserve   │──►│ confirm │<br> │ (T1)    │   │ (T2)    │   │ (T3)      │   │ (T4)    │<br> └─────────┘   └─────────┘   └───────────┘   └─────────┘<br>      ▲             ▲              │<br>      │             │              │ T3 fails (out of stock)<br>      │  C1         │  C2          ▼<br> ┌─────────┐   ┌─────────┐   ┌───────────┐<br> │ ORDER   │◄──│ PAYMENT │◄──│   (no-op: │<br> │ cancel  │   │ refund  │   │  nothing  │<br> │ (C1)    │   │ (C2)    │   │ reserved) │<br> └─────────┘   └─────────┘   └───────────┘<br>   ◄───────────────────────────────────────<br>   Compensation path (reverse order)</pre><p>The key insight: compensations run in <strong>reverse order</strong> of the forward steps that succeeded. If T3 fails, we compensate T2 (refund) then T1 (cancel order). T3 itself produced no committed effect, so it needs no compensation.</p><h3>Choreography vs orchestration</h3><p>There are two ways to coordinate the steps. They are not interchangeable — the choice shapes coupling, observability, and how hard the failure logic is to reason about.</p><p><strong>Choreography</strong> is decentralized. Each service listens for events and reacts by doing its local work and emitting its own event. There is no central brain. Order Service emits OrderCreated; Payment Service hears it, charges, emits PaymentCompleted; Inventory Service hears that, reserves, emits StockReserved; Order Service hears that and confirms. The &quot;workflow&quot; is an emergent property of who subscribes to what.</p><p><strong>Orchestration</strong> is centralized. A dedicated orchestrator (a saga execution coordinator) sends explicit <em>commands</em> (ChargePayment, ReserveStock) and waits for <em>replies</em>. The orchestrator holds the state machine: it knows what step we are on, what to do next, and which compensations to fire on failure.</p><pre>| Dimension | Choreography | Orchestration |<br>|---|---|---|<br>| Coordination | Decentralized, event-driven | Central orchestrator with explicit commands |<br>| Coupling | Looser at runtime, tighter conceptually (everyone knows the event contract) | Services coupled to orchestrator, not to each other |<br>| Workflow visibility | Implicit, scattered across services | Explicit, in one place — easy to read |<br>| Adding a step | Risk of cyclic event dependencies; hard to see full flow | Edit the orchestrator state machine |<br>| Failure/compensation logic | Spread across services, easy to get subtly wrong | Centralized, easier to test exhaustively |<br>| Single point of failure | None inherent | Orchestrator (mitigated by persisting saga state) |<br>| Best for | Short sagas (2–4 steps), few participants | Long or complex sagas, many branches |</pre><p>Rule of thumb: choreography for simple, short flows where the event chain is obvious; orchestration once you have branching, more than ~4 steps, or you need a human to be able to answer “what state is order #4471 in?” without grepping five services.</p><h3>Choreography with Spring Boot 3.x and Kafka</h3><p>We use Kafka as the event backbone. The critical correctness concern is the <strong>dual-write problem</strong>: a service must update its own database <em>and</em> publish an event atomically. If it commits the DB row but crashes before publishing, the saga stalls. The standard fix is the <strong>transactional outbox</strong>: write the event into an outbox table in the <em>same</em> local transaction as the business change, then a separate relay publishes from the outbox to Kafka.</p><p>First, the order entity and the outbox-backed event write. Using Spring’s ApplicationEventPublisher lets us decouple, but the outbox row is what guarantees durability.</p><pre>@Entity<br>@Table(name = &quot;orders&quot;)<br>public class Order {<br>    @Id<br>    private UUID id;</pre><pre>    @Enumerated(EnumType.STRING)<br>    private OrderStatus status; // PENDING, CONFIRMED, CANCELLED</pre><pre>    private String customerId;<br>    private BigDecimal amount;</pre><pre>    @Version<br>    private long version; // optimistic locking guards concurrent updates</pre><pre>    // getters/setters/constructors omitted for brevity<br>}</pre><pre>@Entity<br>@Table(name = &quot;outbox&quot;)<br>public class OutboxEvent {<br>    @Id<br>    private UUID id;<br>    private String aggregateType; // &quot;order&quot;<br>    private String aggregateId;   // order id<br>    private String type;          // &quot;OrderCreated&quot;<br>    @Column(columnDefinition = &quot;jsonb&quot;)<br>    private String payload;<br>    private Instant createdAt;<br>    // getters/setters omitted<br>}</pre><pre>@Service<br>public class OrderService {</pre><pre>    private final OrderRepository orders;<br>    private final OutboxRepository outbox;<br>    private final ObjectMapper mapper;</pre><pre>    public OrderService(OrderRepository orders, OutboxRepository outbox, ObjectMapper mapper) {<br>        this.orders = orders;<br>        this.outbox = outbox;<br>        this.mapper = mapper;<br>    }</pre><pre>    @Transactional<br>    public Order placeOrder(String customerId, BigDecimal amount) {<br>        Order order = new Order(UUID.randomUUID(), OrderStatus.PENDING, customerId, amount);<br>        orders.save(order);</pre><pre>        // Same transaction as the order insert: no dual-write hole.<br>        var event = new OrderCreated(order.getId(), customerId, amount);<br>        outbox.save(new OutboxEvent(<br>                UUID.randomUUID(), &quot;order&quot;, order.getId().toString(),<br>                &quot;OrderCreated&quot;, writeJson(event), Instant.now()));<br>        return order;<br>    }</pre><pre>    private String writeJson(Object o) {<br>        try {<br>            return mapper.writeValueAsString(o);<br>        } catch (JsonProcessingException e) {<br>            throw new IllegalStateException(&quot;event serialization failed&quot;, e);<br>        }<br>    }<br>}</pre><p>In production you would typically let Debezium tail the outbox table&#39;s transaction log straight into Kafka. If you do not want CDC, a simple polling relay is fine for moderate volumes:</p><pre>@Component<br>public class OutboxRelay {</pre><pre>    private final OutboxRepository outbox;<br>    private final KafkaTemplate&lt;String, String&gt; kafka;</pre><pre>    public OutboxRelay(OutboxRepository outbox, KafkaTemplate&lt;String, String&gt; kafka) {<br>        this.outbox = outbox;<br>        this.kafka = kafka;<br>    }</pre><pre>    @Scheduled(fixedDelay = 500)<br>    @Transactional<br>    public void publishPending() {<br>        List&lt;OutboxEvent&gt; batch = outbox.findTop100ByOrderByCreatedAtAsc();<br>        for (OutboxEvent e : batch) {<br>            // topic name derived from aggregate type; key = aggregateId for ordering<br>            kafka.send(e.getAggregateType() + &quot;-events&quot;, e.getAggregateId(), e.getPayload());<br>            outbox.delete(e);<br>        }<br>    }<br>}</pre><p>Note the Kafka <strong>key</strong> is the aggregate id. That preserves per-order ordering on the topic partition, which matters: you do not want PaymentCompleted overtaking OrderCreated for the same order.</p><p>Now the Payment Service consumes OrderCreated and reacts. The consumer must be <strong>idempotent</strong> — Kafka guarantees at-least-once delivery, so the same event can arrive twice.</p><pre>@Component<br>public class PaymentEventHandler {</pre><pre>    private final PaymentRepository payments;<br>    private final ProcessedEventRepository processed;<br>    private final OutboxRepository outbox;<br>    private final ObjectMapper mapper;</pre><pre>    // constructor omitted</pre><pre>    @KafkaListener(topics = &quot;order-events&quot;, groupId = &quot;payment-service&quot;)<br>    @Transactional<br>    public void onOrderEvent(ConsumerRecord&lt;String, String&gt; record) throws Exception {<br>        JsonNode node = mapper.readTree(record.value());<br>        if (!&quot;OrderCreated&quot;.equals(node.get(&quot;type&quot;).asText())) {<br>            return; // not our concern<br>        }<br>        var event = mapper.treeToValue(node, OrderCreated.class);</pre><pre>        // Idempotency guard: dedupe by event id in the same transaction.<br>        if (processed.existsById(event.eventId())) {<br>            return;<br>        }<br>        processed.save(new ProcessedEvent(event.eventId(), Instant.now()));</pre><pre>        try {<br>            payments.save(Payment.charge(event.orderId(), event.amount()));<br>            emit(&quot;PaymentCompleted&quot;, new PaymentCompleted(event.orderId(), event.amount()));<br>        } catch (PaymentDeclinedException ex) {<br>            // No money moved; emit a failure event to trigger compensation upstream.<br>            emit(&quot;PaymentFailed&quot;, new PaymentFailed(event.orderId(), ex.getReason()));<br>        }<br>    }</pre><pre>    private void emit(String type, Object payload) {<br>        outbox.save(new OutboxEvent(UUID.randomUUID(), &quot;payment&quot;,<br>                ((HasOrderId) payload).orderId().toString(), type, writeJson(payload), Instant.now()));<br>    }<br>    // writeJson omitted<br>}</pre><p>Inventory then listens for PaymentCompleted and tries to reserve stock. On failure it emits StockReservationFailed, which Payment Service consumes to issue a refund (its compensation, C2), and Order Service consumes to cancel the order (C1). Each compensation is itself a local transaction that emits its own event — the compensation chain <em>is</em> just more choreography running backwards.</p><p>The danger of choreography is visible here: there is no single place that says “the order saga has these four steps and these three compensations.” A new engineer must trace topic subscriptions across three repos. This is exactly the pain orchestration removes.</p><h3>Orchestration with an explicit coordinator</h3><p>In orchestration, the orchestrator owns a persisted state machine. It sends commands and consumes replies. Persisting saga state is non-negotiable: if the orchestrator restarts mid-saga, it must resume from where it left off, not from the start.</p><pre>@Entity<br>@Table(name = &quot;order_saga_state&quot;)<br>public class OrderSagaState {<br>    @Id<br>    private UUID sagaId;<br>    private UUID orderId;</pre><pre>    @Enumerated(EnumType.STRING)<br>    private SagaStep step; // STARTED, PAYMENT_REQUESTED, STOCK_REQUESTED, COMPLETED, COMPENSATING, FAILED</pre><pre>    private boolean paymentCharged; // track what must be compensated<br>    private boolean stockReserved;</pre><pre>    @Version<br>    private long version;<br>    // getters/setters omitted<br>}</pre><pre>@Service<br>public class OrderSagaOrchestrator {</pre><pre>    private final OrderSagaStateRepository state;<br>    private final CommandGateway commands; // thin wrapper over KafkaTemplate for command topics</pre><pre>    public OrderSagaOrchestrator(OrderSagaStateRepository state, CommandGateway commands) {<br>        this.state = state;<br>        this.commands = commands;<br>    }</pre><pre>    @Transactional<br>    public void start(UUID orderId, BigDecimal amount, String customerId) {<br>        var saga = new OrderSagaState(UUID.randomUUID(), orderId, SagaStep.PAYMENT_REQUESTED);<br>        state.save(saga);<br>        commands.send(&quot;payment-commands&quot;,<br>                new ChargePayment(saga.getSagaId(), orderId, customerId, amount));<br>    }</pre><pre>    @KafkaListener(topics = &quot;payment-replies&quot;, groupId = &quot;order-orchestrator&quot;)<br>    @Transactional<br>    public void onPaymentReply(PaymentReply reply) {<br>        OrderSagaState saga = state.findById(reply.sagaId()).orElseThrow();<br>        if (reply.success()) {<br>            saga.setPaymentCharged(true);<br>            saga.setStep(SagaStep.STOCK_REQUESTED);<br>            commands.send(&quot;inventory-commands&quot;,<br>                    new ReserveStock(saga.getSagaId(), saga.getOrderId()));<br>        } else {<br>            // Payment failed: nothing to compensate yet, just fail the order.<br>            saga.setStep(SagaStep.FAILED);<br>            commands.send(&quot;order-commands&quot;, new CancelOrder(saga.getOrderId()));<br>        }<br>    }</pre><pre>    @KafkaListener(topics = &quot;inventory-replies&quot;, groupId = &quot;order-orchestrator&quot;)<br>    @Transactional<br>    public void onInventoryReply(InventoryReply reply) {<br>        OrderSagaState saga = state.findById(reply.sagaId()).orElseThrow();<br>        if (reply.success()) {<br>            saga.setStockReserved(true);<br>            saga.setStep(SagaStep.COMPLETED);<br>            commands.send(&quot;order-commands&quot;, new ConfirmOrder(saga.getOrderId()));<br>        } else {<br>            // Stock failed: compensate in reverse order. Refund first, then cancel.<br>            saga.setStep(SagaStep.COMPENSATING);<br>            if (saga.isPaymentCharged()) {<br>                commands.send(&quot;payment-commands&quot;,<br>                        new RefundPayment(saga.getSagaId(), saga.getOrderId()));<br>            }<br>            commands.send(&quot;order-commands&quot;, new CancelOrder(saga.getOrderId()));<br>        }<br>    }<br>}</pre><p>The orchestrator reads top-to-bottom like the business process. Adding a fraud-check step is a localized edit to this class plus one new command/reply pair — you do not touch the event subscriptions of unrelated services. The cost is that the orchestrator is now a component every order flows through; you mitigate its single-point-of-failure risk by running multiple instances against the shared, persisted saga state with optimistic locking (@Version).</p><p>For non-trivial systems, do not hand-roll this state machine. <strong>Spring Statemachine</strong>, <strong>Axon Framework</strong>’s saga support, <strong>Eventuate Tram Sagas</strong>, or a workflow engine like <strong>Temporal</strong> or <strong>Camunda</strong> give you persistence, retries, and timeouts out of the box. The hand-rolled version above is for understanding what those libraries are doing.</p><h3>Failure handling: the part that is actually hard</h3><p>The forward path is easy. The value of the saga pattern is in the failure semantics, and there is real vocabulary here.</p><h3>Retriable vs compensatable steps</h3><p>Eventuate’s model (from Chris Richardson’s <em>Microservices Patterns</em>) classifies each step:</p><ul><li><strong>Compensatable transactions</strong> — steps that may need to be undone because a later step can fail. (Create order, Charge payment.)</li><li><strong>Pivot transaction</strong> — the point of no return. Once it commits, the saga <em>will</em> complete forward; there is no going back. (Often the last step that could fail for a business reason — e.g. Reserve stock if everything after it is guaranteed to succeed.)</li><li><strong>Retriable transactions</strong> — steps <em>after</em> the pivot that must eventually succeed and are therefore only ever retried, never compensated. (Confirm order, Send confirmation email.)</li></ul><p>Why this matters: you design the saga so that everything that can fail for a business reason happens <em>before</em> the pivot, and everything after the pivot is something you can guarantee by retrying. Send confirmation email must never be allowed to fail the whole order — it is retriable. You do not refund a customer because an SMTP server was briefly down.</p><h3>Idempotency and retries</h3><p>Every consumer must be idempotent because messaging is at-least-once. We saw the ProcessedEvent dedupe table above. Compensations must <em>also</em> be idempotent — a refund command might be delivered twice, and you must refund only once. Track compensation state explicitly (the paymentCharged flag), and make the refund a no-op if it has already run.</p><p>Compensations themselves can fail (the payment gateway is down during a refund). The rule: <strong>compensations must be retried until they succeed</strong>, with backoff and a dead-letter queue plus alerting as the last resort. A compensation that gives up leaves the system in an inconsistent state — money taken, order cancelled. This is the worst outcome and warrants a human.</p><h3>Semantic locks: closing the isolation hole</h3><p>Here is the uncomfortable truth: <strong>sagas are not isolated.</strong> While a saga is in flight, its partial, uncommitted-from-a-business-view changes are visible to everyone. A monolithic transaction would hide them until commit. The classic anomalies (from the same literature):</p><ul><li><strong>Lost updates</strong> — saga A overwrites a change made by saga B without seeing it.</li><li><strong>Dirty reads</strong> — saga B reads data that saga A has changed but later compensates away. B made a decision on data that “never really happened.”</li><li><strong>Fuzzy / non-repeatable reads</strong> — different steps of the same saga read the same data and get different answers because another saga modified it in between.</li></ul><p>The standard countermeasure is the <strong>semantic lock</strong>: instead of locking rows at the database level, you mark the affected business entity with an application-level flag indicating an in-progress saga. The Order.status = PENDING is exactly this — it tells the rest of the system &quot;this order is not finished; do not treat it as confirmed.&quot; A stock reservation is another semantic lock: the quantity is moved to a RESERVED bucket, visible as unavailable, but not yet SOLD. If the saga compensates, the reservation is released.</p><pre>@Service<br>public class InventoryService {</pre><pre>    private final StockRepository stock;</pre><pre>    public InventoryService(StockRepository stock) {<br>        this.stock = stock;<br>    }</pre><pre>    @Transactional<br>    public boolean reserve(UUID orderId, String sku, int qty) {<br>        // Optimistic-locked read-modify-write avoids lost updates on hot SKUs.<br>        StockItem item = stock.findBySku(sku).orElseThrow();<br>        if (item.available() &lt; qty) {<br>            return false; // triggers StockReservationFailed -&gt; compensation<br>        }<br>        // Semantic lock: move to RESERVED, not SOLD. Visible as unavailable to others.<br>        item.reserve(orderId, qty);<br>        stock.save(item); // @Version on StockItem throws on concurrent modification -&gt; retry<br>        return true;<br>    }</pre><pre>    @Transactional<br>    public void releaseReservation(UUID orderId, String sku, int qty) {<br>        // Compensation C3 — idempotent: only release if a reservation exists.<br>        stock.findBySku(sku).ifPresent(item -&gt; {<br>            if (item.hasReservation(orderId)) {<br>                item.release(orderId, qty);<br>                stock.save(item);<br>            }<br>        });<br>    }<br>}</pre><p>Other countermeasures worth knowing: <strong>commutative updates</strong> (design operations so order does not matter — credit/debit instead of setBalance), <strong>pessimistic views</strong> (reorder steps so the riskiest comes first, shrinking the window of inconsistency), and <strong>re-reads / version checks</strong> before compensating to detect intervening changes. None of these gives you true isolation; they reduce the probability and blast radius of anomalies. You must decide, per saga, which anomalies your business can tolerate.</p><h3>When to use the saga pattern — and when not to</h3><p>Use a saga when:</p><ul><li>A business operation spans <strong>multiple services with separate databases</strong> and you need an all-or-nothing <em>business</em> outcome.</li><li>The participants cannot or should not be coupled by 2PC (third-party APIs, message brokers, high-throughput hot rows).</li><li>You can tolerate <strong>eventual consistency</strong> and brief windows where partial results are visible.</li></ul><p>Do <strong>not</strong> reach for a saga when:</p><ul><li>The data belongs together. If three services are always changing in one transaction, that is a hint your service boundaries are wrong. The cheapest fix is often to <strong>merge them into one service</strong> with one database and one ordinary @Transactional — no saga needed.</li><li>You need <strong>strong isolation / read-your-writes</strong> semantics for the in-flight operation. Sagas cannot give you serializability across services.</li><li>The operation is genuinely <strong>single-service</strong>. Do not build a saga for something a local transaction handles.</li></ul><h3>Pitfalls checklist</h3><ul><li><strong>Dual-write without an outbox.</strong> Committing the DB then publishing as two separate operations <em>will</em> eventually drop or duplicate events. Always use a transactional outbox (or CDC) so the event and the state change share one transaction.</li><li><strong>Non-idempotent consumers and compensations.</strong> At-least-once delivery means everything runs more than once. Dedupe by event id; make compensations no-ops when already applied.</li><li><strong>Forgetting that compensations can fail.</strong> Retry them forever (with backoff) and alert; never silently drop.</li><li><strong>Ignoring isolation anomalies.</strong> Audit every saga for dirty reads and lost updates. Add semantic locks (status flags, reservations) deliberately, not by accident.</li><li><strong>Choreography sprawl.</strong> Beyond ~4 steps or any branching, the implicit event web becomes unmaintainable and prone to cyclic dependencies. Switch to orchestration.</li><li><strong>No saga timeout.</strong> A step’s reply may never come. Set per-step timeouts so a stuck saga triggers compensation instead of hanging forever.</li><li><strong>Treating retriable steps as compensatable.</strong> Do not unwind a customer’s whole order because a non-critical post-pivot step (email, analytics) failed. Retry it.</li></ul><h3>Closing thought</h3><p>Sagas are not a free upgrade from 2PC — they are a different deal. You trade global atomicity-with-isolation for atomicity-without-isolation, and you pay for it in idempotency code, compensation logic, and careful anomaly analysis. Done well, they let independent services stay independent while still delivering coherent business outcomes. Done carelessly, they leave money charged against cancelled orders. The difference is entirely in how seriously you take the failure path.</p><p>If this breakdown helped, follow the <strong>devdomain</strong> publication for more deep dives into distributed systems and backend architecture. Have you run sagas in production — choreography or orchestration, and what bit you? Drop your war stories in the comments; the messy real-world cases are the best part.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ce910ef5beb6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devdomain/the-saga-pattern-managing-distributed-transactions-across-microservicesyou-split-a-monolith-into-ce910ef5beb6">The Saga Pattern: Managing Distributed Transactions Across MicroservicesYou split a monolith into…</a> was originally published in <a href="https://medium.com/devdomain">devdomain</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Union & Literal Types ]]></title>
            <link>https://medium.com/the-60-second-programmer/union-literal-types-9a8e590d4dcb?source=rss-f417e4e45a5e------2</link>
            <guid isPermaLink="false">https://medium.com/p/9a8e590d4dcb</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[type]]></category>
            <category><![CDATA[unions]]></category>
            <category><![CDATA[type-safety]]></category>
            <dc:creator><![CDATA[Marcelo Domingues]]></dc:creator>
            <pubDate>Mon, 02 Nov 2026 09:21:01 GMT</pubDate>
            <atom:updated>2026-11-02T09:21:01.751Z</atom:updated>
            <content:encoded><![CDATA[<h4>Model ‘one of these exact values’ precisely.</h4><p>A union (|) says a value is <em>one of several</em> types. A literal type narrows it to <em>one exact value</em>. Together they&#39;re TypeScript&#39;s superpower.</p><h3>Literal types</h3><p>Instead of string, accept only specific strings:</p><pre>type Direction = &quot;up&quot; | &quot;down&quot; | &quot;left&quot; | &quot;right&quot;;</pre><pre>function move(d: Direction) { /* ... */ }</pre><pre>move(&quot;up&quot;);   // ok<br>move(&quot;north&quot;); // error: not a Direction</pre><h3>Narrowing</h3><p>TypeScript narrows the type as you check it:</p><pre>type Status = &quot;loading&quot; | &quot;success&quot; | &quot;error&quot;;</pre><pre>function render(s: Status) {<br>  if (s === &quot;error&quot;) {<br>    // s is &quot;error&quot; here<br>    return &quot;Something broke&quot;;<br>  }<br>  return s; // s is &quot;loading&quot; | &quot;success&quot;<br>}</pre><p>Combine with switch for exhaustive handling — every case covered, compiler-verified.</p><p><strong>TL;DR:</strong> Unions + literals replace loose string/number with a precise set of allowed values.</p><p>Swap a stringly-typed parameter for a literal union and watch the bugs vanish.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9a8e590d4dcb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-60-second-programmer/union-literal-types-9a8e590d4dcb">Union &amp; Literal Types 🔀</a> was originally published in <a href="https://medium.com/the-60-second-programmer">The 60-Second Programmer</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Goroutines Explained ]]></title>
            <link>https://medium.com/the-60-second-programmer/goroutines-explained-5ee853a93ea3?source=rss-f417e4e45a5e------2</link>
            <guid isPermaLink="false">https://medium.com/p/5ee853a93ea3</guid>
            <category><![CDATA[goroutines]]></category>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[concurrency]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[Marcelo Domingues]]></dc:creator>
            <pubDate>Fri, 30 Oct 2026 09:16:01 GMT</pubDate>
            <atom:updated>2026-10-30T09:16:01.574Z</atom:updated>
            <content:encoded><![CDATA[<h4>Concurrency that costs almost nothing to start.</h4><p>Want to run something concurrently in Go? Put go in front of it. Done.</p><h3>What is a goroutine?</h3><p>A goroutine is a lightweight thread managed by the Go runtime. They start with a tiny stack (a few KB) and grow as needed, so you can spawn thousands without breaking a sweat. The scheduler multiplexes them onto OS threads for you.</p><h3>Starting one</h3><pre>package main</pre><pre>import (<br>	&quot;fmt&quot;<br>	&quot;sync&quot;<br>)</pre><pre>func main() {<br>	var wg sync.WaitGroup<br>	for i := 0; i</pre><h3>Watch out</h3><p>main returning kills every goroutine, so coordinate with sync.WaitGroup or a channel. Goroutines run independently. Use channels or mutexes to share data safely. Capture loop variables explicitly (pass i as an argument) unless you&#39;re on Go 1.22+.</p><p><strong>TL;DR:</strong> go f() launches concurrent work; use WaitGroup or channels to wait for it.</p><p>Try spawning 1,000 goroutines and watch how fast they finish. 🐹</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5ee853a93ea3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-60-second-programmer/goroutines-explained-5ee853a93ea3">Goroutines Explained 🚀</a> was originally published in <a href="https://medium.com/the-60-second-programmer">The 60-Second Programmer</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spring Boot Error Handling: @ControllerAdvice and ProblemDetail (RFC 7807)Most APIs leak.]]></title>
            <link>https://medium.com/devdomain/spring-boot-error-handling-controlleradvice-and-problemdetail-rfc-7807-most-apis-leak-54c78245a22a?source=rss-f417e4e45a5e------2</link>
            <guid isPermaLink="false">https://medium.com/p/54c78245a22a</guid>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[problemdetails]]></category>
            <category><![CDATA[error-handling]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[Marcelo Domingues]]></dc:creator>
            <pubDate>Thu, 29 Oct 2026 19:46:01 GMT</pubDate>
            <atom:updated>2026-10-29T19:46:01.790Z</atom:updated>
            <content:encoded><![CDATA[<p>Spring Boot Error Handling: <a href="http://twitter.com/ControllerAdvice">@ControllerAdvice</a> and ProblemDetail (RFC 7807)Most APIs leak. Not data — <em>error shapes</em>. One endpoint returns {&quot;error&quot;: &quot;not found&quot;}, another returns {&quot;message&quot;: &quot;...&quot;, &quot;code&quot;: 42}, a validation failure dumps Spring&#39;s default Whitelabel-style payload, and an unhandled exception returns a 500 with a stack trace the client can&#39;t use. Every consumer writes bespoke parsing for every endpoint, and your support channel fills with &quot;the API returned something weird.&quot;</p><p>The fix is a single, consistent error contract enforced in one place. Spring Boot gives you the tools: @ControllerAdvice for centralized handling, @ExceptionHandler for mapping exceptions to responses, and — since Spring 6 / Boot 3 — first-class support for <strong>ProblemDetail</strong>, the standardized error format defined by <strong>RFC 7807 (now RFC 9457)</strong>. This article builds a complete, production-grade error-handling layer.</p><h3>The problem with scattered error handling</h3><p>Without a strategy, error handling spreads across controllers:</p><pre>// DON&#39;T: error handling tangled into business code<br>@GetMapping(&quot;/orders/{id}&quot;)<br>public ResponseEntity&lt;?&gt; getOrder(@PathVariable Long id) {<br>    Order o = repo.findById(id).orElse(null);<br>    if (o == null) {<br>        return ResponseEntity.status(404)<br>            .body(Map.of(&quot;error&quot;, &quot;order not found&quot;));<br>    }<br>    return ResponseEntity.ok(o);<br>}</pre><p>Every controller reinvents the wheel, formats differ, and cross-cutting concerns (logging, correlation IDs) get copy-pasted. Centralize instead: let controllers throw meaningful exceptions and translate them in one advice.</p><h3>RFC 7807: a standard error shape</h3><p>RFC 7807 (“Problem Details for HTTP APIs”) defines a media type, application/problem+json, and a canonical structure:</p><pre>| Field | Meaning |<br>|---|---|<br>| `type` | A URI identifying the problem type (a stable, dereferenceable category) |<br>| `title` | A short, human-readable summary of the problem type |<br>| `status` | The HTTP status code |<br>| `detail` | A human-readable explanation specific to this occurrence |<br>| `instance` | A URI identifying this specific occurrence |</pre><p>Plus any <strong>extension members</strong> you add (e.g. errors, traceId). A compliant response looks like:</p><pre>{<br>  &quot;type&quot;: &quot;https://api.example.com/problems/order-not-found&quot;,<br>  &quot;title&quot;: &quot;Order Not Found&quot;,<br>  &quot;status&quot;: 404,<br>  &quot;detail&quot;: &quot;No order exists with id 4711&quot;,<br>  &quot;instance&quot;: &quot;/orders/4711&quot;,<br>  &quot;traceId&quot;: &quot;0af7651916cd43dd8448eb211c80319c&quot;<br>}</pre><p>Spring 6 models this with the org.springframework.http.ProblemDetail class — no third-party library required.</p><h3>ProblemDetail by hand</h3><p>ProblemDetail is a plain holder you can build and return directly:</p><pre>import org.springframework.http.HttpStatus;<br>import org.springframework.http.ProblemDetail;<br>import java.net.URI;</pre><pre>ProblemDetail pd = ProblemDetail.forStatusAndDetail(<br>    HttpStatus.NOT_FOUND, &quot;No order exists with id 4711&quot;);<br>pd.setType(URI.create(&quot;https://api.example.com/problems/order-not-found&quot;));<br>pd.setTitle(&quot;Order Not Found&quot;);<br>pd.setProperty(&quot;traceId&quot;, &quot;0af7651916cd43dd8448eb211c80319c&quot;); // extension member</pre><p>setProperty adds arbitrary extension fields, serialized flat alongside the standard ones.</p><h3>Turning it on for built-in exceptions</h3><p>Spring MVC throws a family of internal exceptions (e.g. HttpMessageNotReadableException, MethodArgumentNotValidException, NoResourceFoundException). To make Spring render <em>those</em> as ProblemDetail automatically, enable the property:</p><pre>spring.mvc.problemdetails.enabled=true</pre><p>(WebFlux uses spring.webflux.problemdetails.enabled=true.) With this on, framework-level errors already come back as RFC 7807 documents. Your job is to handle <em>your</em> exceptions consistently and enrich the defaults.</p><h3>Global handling with @RestControllerAdvice</h3><p>@ControllerAdvice (or @RestControllerAdvice, which adds @ResponseBody) registers exception handlers that apply across all controllers. Combined with ProblemDetail returns, it&#39;s the backbone of a clean contract.</p><p>Define domain exceptions first:</p><pre>public class ResourceNotFoundException extends RuntimeException {<br>    public ResourceNotFoundException(String message) { super(message); }<br>}</pre><pre>public class BusinessRuleException extends RuntimeException {<br>    private final String code;<br>    public BusinessRuleException(String code, String message) {<br>        super(message);<br>        this.code = code;<br>    }<br>    public String getCode() { return code; }<br>}</pre><p>Now the central advice:</p><pre>import org.springframework.http.HttpStatus;<br>import org.springframework.http.ProblemDetail;<br>import org.springframework.web.bind.annotation.ExceptionHandler;<br>import org.springframework.web.bind.annotation.RestControllerAdvice;<br>import org.springframework.web.context.request.WebRequest;<br>import org.slf4j.Logger;<br>import org.slf4j.LoggerFactory;<br>import java.net.URI;<br>import java.time.Instant;</pre><pre>@RestControllerAdvice<br>public class GlobalExceptionHandler {</pre><pre>    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);<br>    private static final String BASE = &quot;https://api.example.com/problems/&quot;;</pre><pre>    @ExceptionHandler(ResourceNotFoundException.class)<br>    public ProblemDetail handleNotFound(ResourceNotFoundException ex) {<br>        ProblemDetail pd = ProblemDetail.forStatusAndDetail(<br>            HttpStatus.NOT_FOUND, ex.getMessage());<br>        pd.setType(URI.create(BASE + &quot;resource-not-found&quot;));<br>        pd.setTitle(&quot;Resource Not Found&quot;);<br>        pd.setProperty(&quot;timestamp&quot;, Instant.now());<br>        return pd;<br>    }</pre><pre>    @ExceptionHandler(BusinessRuleException.class)<br>    public ProblemDetail handleBusinessRule(BusinessRuleException ex) {<br>        ProblemDetail pd = ProblemDetail.forStatusAndDetail(<br>            HttpStatus.UNPROCESSABLE_ENTITY, ex.getMessage());<br>        pd.setType(URI.create(BASE + &quot;business-rule-violation&quot;));<br>        pd.setTitle(&quot;Business Rule Violation&quot;);<br>        pd.setProperty(&quot;code&quot;, ex.getCode());<br>        return pd;<br>    }</pre><pre>    // Catch-all: never leak stack traces to clients<br>    @ExceptionHandler(Exception.class)<br>    public ProblemDetail handleUnexpected(Exception ex) {<br>        log.error(&quot;Unhandled exception&quot;, ex); // log full detail server-side<br>        ProblemDetail pd = ProblemDetail.forStatusAndDetail(<br>            HttpStatus.INTERNAL_SERVER_ERROR,<br>            &quot;An unexpected error occurred. Please contact support.&quot;);<br>        pd.setType(URI.create(BASE + &quot;internal-error&quot;));<br>        pd.setTitle(&quot;Internal Server Error&quot;);<br>        return pd;<br>    }<br>}</pre><p>Two principles already baked in: <strong>never expose internal exception messages or stack traces to clients</strong> (the catch-all returns a generic detail while logging the real cause), and <strong>map each exception type to a meaningful status</strong>.</p><h3>Handling validation errors properly</h3><p>Bean Validation (jakarta.validation) is where consistent error responses pay off most, because clients need field-level detail to fix their input.</p><p>Annotate your request DTO:</p><pre>import jakarta.validation.constraints.*;</pre><pre>public record CreateUserRequest(<br>    @NotBlank String username,<br>    @Email String email,<br>    @Min(18) int age,<br>    @Size(min = 8) String password<br>) {}</pre><p>Trigger validation with @Valid:</p><pre>@PostMapping(&quot;/users&quot;)<br>public ResponseEntity&lt;UserResponse&gt; create(@Valid @RequestBody CreateUserRequest req) {<br>    // if invalid, MethodArgumentNotValidException is thrown before this runs<br>    return ResponseEntity.ok(service.create(req));<br>}</pre><p>When validation fails, Spring throws MethodArgumentNotValidException. To emit a clean RFC 7807 response with per-field errors, <strong>extend </strong><strong>ResponseEntityExceptionHandler</strong> — it already handles the Spring MVC exceptions and lets you customize them, returning ProblemDetail bodies:</p><pre>import org.springframework.http.HttpHeaders;<br>import org.springframework.http.HttpStatusCode;<br>import org.springframework.http.ProblemDetail;<br>import org.springframework.http.ResponseEntity;<br>import org.springframework.web.bind.MethodArgumentNotValidException;<br>import org.springframework.web.bind.annotation.RestControllerAdvice;<br>import org.springframework.web.context.request.WebRequest;<br>import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;<br>import java.net.URI;<br>import java.util.ArrayList;<br>import java.util.List;</pre><pre>@RestControllerAdvice<br>public class ValidationExceptionHandler extends ResponseEntityExceptionHandler {</pre><pre>    @Override<br>    protected ResponseEntity&lt;Object&gt; handleMethodArgumentNotValid(<br>            MethodArgumentNotValidException ex,<br>            HttpHeaders headers,<br>            HttpStatusCode status,<br>            WebRequest request) {</pre><pre>        List&lt;FieldError&gt; errors = new ArrayList&lt;&gt;();<br>        ex.getBindingResult().getFieldErrors().forEach(fe -&gt;<br>            errors.add(new FieldError(fe.getField(), fe.getDefaultMessage())));<br>        ex.getBindingResult().getGlobalErrors().forEach(ge -&gt;<br>            errors.add(new FieldError(ge.getObjectName(), ge.getDefaultMessage())));</pre><pre>        ProblemDetail pd = ProblemDetail.forStatusAndDetail(<br>            org.springframework.http.HttpStatus.BAD_REQUEST,<br>            &quot;Request validation failed&quot;);<br>        pd.setType(URI.create(&quot;https://api.example.com/problems/validation-error&quot;));<br>        pd.setTitle(&quot;Validation Error&quot;);<br>        pd.setProperty(&quot;errors&quot;, errors);</pre><pre>        return ResponseEntity.badRequest().body(pd);<br>    }</pre><pre>    public record FieldError(String field, String message) {}<br>}</pre><p>Resulting payload:</p><pre>{<br>  &quot;type&quot;: &quot;https://api.example.com/problems/validation-error&quot;,<br>  &quot;title&quot;: &quot;Validation Error&quot;,<br>  &quot;status&quot;: 400,<br>  &quot;detail&quot;: &quot;Request validation failed&quot;,<br>  &quot;errors&quot;: [<br>    { &quot;field&quot;: &quot;email&quot;, &quot;message&quot;: &quot;must be a well-formed email address&quot; },<br>    { &quot;field&quot;: &quot;age&quot;, &quot;message&quot;: &quot;must be greater than or equal to 18&quot; }<br>  ]<br>}</pre><h3>Handling constraint violations on path/query params</h3><p>@Valid on a body throws MethodArgumentNotValidException. But validating method parameters directly (e.g. @RequestParam @Min(1) int page with @Validated on the controller) throws jakarta.validation.ConstraintViolationException instead — a different exception. Handle it too:</p><pre>import jakarta.validation.ConstraintViolationException;<br>import org.springframework.web.bind.annotation.ExceptionHandler;</pre><pre>@ExceptionHandler(ConstraintViolationException.class)<br>public ProblemDetail handleConstraintViolation(ConstraintViolationException ex) {<br>    var errors = ex.getConstraintViolations().stream()<br>        .map(v -&gt; new FieldError(v.getPropertyPath().toString(), v.getMessage()))<br>        .toList();<br>    ProblemDetail pd = ProblemDetail.forStatusAndDetail(<br>        org.springframework.http.HttpStatus.BAD_REQUEST, &quot;Constraint violation&quot;);<br>    pd.setProperty(&quot;errors&quot;, errors);<br>    return pd;<br>}</pre><h3>ErrorResponseException and ProblemDetail-aware exceptions</h3><p>Spring 6 introduced ErrorResponse and ErrorResponseException, which carry their own ProblemDetail. You can make domain exceptions self-describing by implementing ErrorResponse, so they render correctly even without a dedicated handler:</p><pre>import org.springframework.http.HttpStatus;<br>import org.springframework.web.ErrorResponseException;<br>import org.springframework.http.ProblemDetail;<br>import java.net.URI;</pre><pre>public class OrderNotFoundException extends ErrorResponseException {<br>    public OrderNotFoundException(Long id) {<br>        super(HttpStatus.NOT_FOUND, problem(id), null);<br>    }<br>    private static ProblemDetail problem(Long id) {<br>        ProblemDetail pd = ProblemDetail.forStatusAndDetail(<br>            HttpStatus.NOT_FOUND, &quot;No order exists with id &quot; + id);<br>        pd.setType(URI.create(&quot;https://api.example.com/problems/order-not-found&quot;));<br>        pd.setTitle(&quot;Order Not Found&quot;);<br>        return pd;<br>    }<br>}</pre><p>Throwing this from a controller produces a correct application/problem+json response with no handler at all — Spring recognizes ErrorResponse and renders it.</p><h3>Enriching every response: instance URI and trace IDs</h3><p>Clients and support teams need to correlate an error to a log line. Add the request path as instance and a trace ID as an extension. A small helper on the advice keeps it DRY:</p><pre>import org.springframework.web.context.request.ServletWebRequest;<br>import org.springframework.web.context.request.WebRequest;<br>import java.net.URI;</pre><pre>private void enrich(ProblemDetail pd, WebRequest request) {<br>    if (request instanceof ServletWebRequest swr) {<br>        pd.setInstance(URI.create(swr.getRequest().getRequestURI()));<br>    }<br>    // From MDC / tracing context (e.g. Micrometer Tracing populates this)<br>    String traceId = org.slf4j.MDC.get(&quot;traceId&quot;);<br>    if (traceId != null) pd.setProperty(&quot;traceId&quot;, traceId);<br>}</pre><p>Call enrich(pd, request) at the end of each handler (add WebRequest request to the handler signatures). Now every error is traceable.</p><h3>Scoping advice and ordering multiple handlers</h3><p>@RestControllerAdvice applies globally by default, but you can scope it — useful in modular applications where different packages need different error contracts (e.g. a public API vs an internal admin API):</p><pre>// Only applies to controllers in the public API package<br>@RestControllerAdvice(basePackages = &quot;com.example.api.public_&quot;)<br>public class PublicApiExceptionHandler { ... }</pre><pre>// Only applies to controllers annotated with @AdminApi<br>@RestControllerAdvice(annotations = AdminApi.class)<br>public class AdminApiExceptionHandler { ... }</pre><p>When more than one advice can handle the same exception type, Spring resolves by @Order (lower value wins) and by handler specificity — a handler for ResourceNotFoundException beats one for RuntimeException. Keep this predictable: prefer a single advice per concern, and only layer them when you have a genuine reason (a fallback advice with @Order(Ordered.LOWEST_PRECEDENCE) catching Exception is a common, legitimate pattern).</p><h3>How handler method resolution works</h3><p>When an exception escapes a controller, Spring’s ExceptionHandlerExceptionResolver walks the advice beans and looks for the <em>most specific</em> @ExceptionHandler whose declared types match the thrown exception (or a cause, when configured). Matching considers the exception hierarchy, so a handler for a superclass catches subclasses unless a more specific handler exists. This is why a catch-all @ExceptionHandler(Exception.class) reliably acts as a backstop: it matches everything, but loses to any narrower handler.</p><pre>exception thrown in controller<br>        |<br>        v<br>ExceptionHandlerExceptionResolver<br>        |<br>        v<br>find most specific @ExceptionHandler across @ControllerAdvice beans<br>        |<br>        +-- match found  -&gt; invoke handler -&gt; render ProblemDetail<br>        |<br>        +-- no match     -&gt; propagate to container -&gt; default error page / 500</pre><h3>Content negotiation and the problem+json media type</h3><p>When you return a ProblemDetail, Spring serializes it as application/problem+json (for JSON) or application/problem+xml (for XML), per the RFC. Clients that inspect the Content-Type can branch on it to detect an error envelope without parsing the body first. This is one of the quiet advantages of the standard: the <em>media type itself</em> signals &quot;this is a problem document,&quot; which generic HTTP clients and gateways can act on.</p><p>If you instead return a hand-rolled Map or custom DTO, you lose that signal and the standard structure, and every consumer must special-case your shape. Returning ProblemDetail is therefore not just cleaner code — it&#39;s a more interoperable contract.</p><h3>Testing your error contract</h3><p>Error handling is exactly the kind of code that rots silently, so test it. A @WebMvcTest slice exercises the advice without booting the full context:</p><pre>import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;<br>import org.springframework.beans.factory.annotation.Autowired;<br>import org.springframework.test.web.servlet.MockMvc;<br>import org.junit.jupiter.api.Test;</pre><pre>import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;<br>import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;</pre><pre>@WebMvcTest(OrderController.class)<br>class OrderErrorContractTest {</pre><pre>    @Autowired MockMvc mvc;</pre><pre>    @Test<br>    void missingOrder_returnsProblemDetail() throws Exception {<br>        mvc.perform(get(&quot;/orders/4711&quot;))<br>           .andExpect(status().isNotFound())<br>           .andExpect(content().contentType(&quot;application/problem+json&quot;))<br>           .andExpect(jsonPath(&quot;$.title&quot;).value(&quot;Order Not Found&quot;))<br>           .andExpect(jsonPath(&quot;$.status&quot;).value(404))<br>           .andExpect(jsonPath(&quot;$.type&quot;).exists());<br>    }<br>}</pre><p>Asserting on Content-Type and the standard fields locks down the contract so a future refactor can&#39;t quietly change the error shape your clients depend on.</p><h3>A summary of the mapping strategy</h3><pre>| Exception | HTTP status | Where handled |<br>|---|---|---|<br>| `ResourceNotFoundException` (yours) | 404 | `@ExceptionHandler` |<br>| `BusinessRuleException` (yours) | 422 | `@ExceptionHandler` |<br>| `MethodArgumentNotValidException` | 400 | Override in `ResponseEntityExceptionHandler` |<br>| `ConstraintViolationException` | 400 | `@ExceptionHandler` |<br>| `HttpMessageNotReadableException` | 400 | Built-in (problemdetails enabled) |<br>| `AccessDeniedException` | 403 | Security layer / advice |<br>| Anything else | 500 | Catch-all `@ExceptionHandler(Exception.class)` |</pre><h3>Common pitfalls</h3><ul><li><strong>Leaking internals</strong>: returning ex.getMessage() from a generic 500 can expose SQL, file paths, or class names. The catch-all must return a generic message and log the real cause.</li><li><strong>Two handlers for the same exception</strong>: if both a plain @RestControllerAdvice and a ResponseEntityExceptionHandler subclass handle the same type, ordering is ambiguous. Keep one source of truth; use @Order on advices if you must layer them.</li><li><strong>Forgetting </strong><strong>@Valid</strong>: validation annotations on a DTO do nothing without @Valid (body) or @Validated (params) at the call site.</li><li><strong>Confusing the two validation exceptions</strong>: body validation throws MethodArgumentNotValidException; param validation throws ConstraintViolationException. Handle both.</li><li><strong>Not enabling </strong><strong>spring.mvc.problemdetails.enabled</strong>: without it, built-in framework errors won&#39;t be RFC 7807 by default.</li><li><strong>Returning the wrong status for business errors</strong>: 422 (Unprocessable Entity) often fits domain-rule failures better than 400 (malformed request). Choose intentionally.</li><li><strong>Self-invocation / exceptions thrown outside the dispatcher</strong>: @ControllerAdvice only catches exceptions propagating out of controller handling. Errors in filters or async threads need separate handling.</li><li><strong>Inconsistent </strong><strong>type URIs</strong>: keep them stable and documented; clients may branch on type, so changing it is a breaking change.</li></ul><h3>Best practices</h3><ul><li>One @RestControllerAdvice per concern (e.g. domain errors, validation), each focused and small.</li><li>Use ProblemDetail everywhere so the contract is uniform and standards-compliant (application/problem+json).</li><li>Make type URIs stable and resolvable to documentation; treat them as part of your API contract.</li><li>Always include instance and a traceId for correlation.</li><li>Map exceptions to <em>semantic</em> status codes; never default everything to 400 or 500.</li><li>Validate at the edge with Bean Validation and surface field-level errors as an errors extension array.</li><li>Log full detail server-side; return only safe, actionable detail to clients.</li><li>Cover the catch-all so an unexpected exception never escapes as a stack trace.</li></ul><h3>Conclusion</h3><p>Consistent error handling is a contract, not an afterthought. Spring Boot 3 makes that contract cheap to honor: @RestControllerAdvice centralizes mapping, @ExceptionHandler ties exceptions to responses, and built-in ProblemDetail gives you RFC 7807 / 9457 compliance without a library. Enable spring.mvc.problemdetails.enabled, extend ResponseEntityExceptionHandler for validation, enrich every response with an instance URI and traceId, and guard against leaks with a generic catch-all. The result is an API where every error — from a missing resource to an invalid field to a catastrophic 500 — speaks the same predictable language.</p><p>If this helped you standardize your API’s errors, <strong>follow devdomain</strong> for more Spring Boot deep dives, and drop a comment with the ugliest error payload you’ve had to consume. Misery loves company.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=54c78245a22a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devdomain/spring-boot-error-handling-controlleradvice-and-problemdetail-rfc-7807-most-apis-leak-54c78245a22a">Spring Boot Error Handling: @ControllerAdvice and ProblemDetail (RFC 7807)Most APIs leak.</a> was originally published in <a href="https://medium.com/devdomain">devdomain</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LLMs Explained ]]></title>
            <link>https://medium.com/the-60-second-programmer/llms-explained-13a6d2493fec?source=rss-f417e4e45a5e------2</link>
            <guid isPermaLink="false">https://medium.com/p/13a6d2493fec</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[fundamentals]]></category>
            <dc:creator><![CDATA[Marcelo Domingues]]></dc:creator>
            <pubDate>Wed, 28 Oct 2026 09:06:01 GMT</pubDate>
            <atom:updated>2026-10-28T09:06:01.493Z</atom:updated>
            <content:encoded><![CDATA[<h4>What a large language model actually does, in plain terms.</h4><p>An <strong>LLM</strong> (Large Language Model) is a neural network trained on huge amounts of text to predict the next token. That’s it. Everything else — answering questions, writing code, summarizing — emerges from doing that one thing extremely well.</p><h3>How it works</h3><p>Your text is split into <strong>tokens</strong> (roughly word-fragments). The model assigns a probability to every possible next token, picks one, appends it, and repeats. A paragraph is just the model running that prediction step hundreds of times.</p><pre>&quot;The capital of France is&quot; → [Paris: 0.92] [Lyon: 0.01] ... → &quot;Paris&quot;</pre><h3>What it is NOT</h3><p>Not a database — it doesn’t “look up” facts; it predicts plausible text, so it can be wrong (a “hallucination”). Not deterministic by default — sampling makes outputs vary between runs. Not stateful — each request is independent. You resend the full conversation every time.</p><h3>Calling one</h3><pre>import anthropic</pre><pre>client = anthropic.Anthropic()<br>resp = client.messages.create(<br>    model=&quot;claude-opus-4-8&quot;,<br>    max_tokens=1024,<br>    messages=[{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;Explain an LLM in one sentence.&quot;}],<br>)<br>print(resp.content[0].text)</pre><p><strong>TL;DR:</strong> An LLM is a next-token predictor trained at scale — powerful, but it generates plausible text, not verified truth.</p><p>Try one against a real prompt before you trust it for anything that matters. →</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=13a6d2493fec" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-60-second-programmer/llms-explained-13a6d2493fec">LLMs Explained 🧠</a> was originally published in <a href="https://medium.com/the-60-second-programmer">The 60-Second Programmer</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rust for Backend Developers: Why People Are Rewriting Everything in It]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@marcelogdomingues/rust-for-backend-developers-why-people-are-rewriting-everything-in-it-5edc33cc04c0?source=rss-f417e4e45a5e------2"><img src="https://cdn-images-1.medium.com/max/1221/1*iNnTJR8hLXiXremDt9LhIw.png" width="1221"></a></p><p class="medium-feed-snippet">Memory safety without a garbage collector, fearless concurrency, and the honest truth about the learning curve.</p><p class="medium-feed-link"><a href="https://medium.com/@marcelogdomingues/rust-for-backend-developers-why-people-are-rewriting-everything-in-it-5edc33cc04c0?source=rss-f417e4e45a5e------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@marcelogdomingues/rust-for-backend-developers-why-people-are-rewriting-everything-in-it-5edc33cc04c0?source=rss-f417e4e45a5e------2</link>
            <guid isPermaLink="false">https://medium.com/p/5edc33cc04c0</guid>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[backend]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[Marcelo Domingues]]></dc:creator>
            <pubDate>Wed, 28 Oct 2026 02:11:01 GMT</pubDate>
            <atom:updated>2026-10-28T02:11:01.722Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Spring Batch: Processing Millions of Records ReliablyIt always starts simple.]]></title>
            <link>https://medium.com/devdomain/spring-batch-processing-millions-of-records-reliablyit-always-starts-simple-5c86937152c1?source=rss-f417e4e45a5e------2</link>
            <guid isPermaLink="false">https://medium.com/p/5c86937152c1</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[spring-batch]]></category>
            <dc:creator><![CDATA[Marcelo Domingues]]></dc:creator>
            <pubDate>Tue, 27 Oct 2026 18:06:01 GMT</pubDate>
            <atom:updated>2026-10-27T18:06:01.745Z</atom:updated>
            <content:encoded><![CDATA[<p>Spring Batch: Processing Millions of Records ReliablyIt always starts simple. “Read this 50,000-row CSV and insert it into the database.” So you write a for loop, load the whole file into a List, and call saveAll. It works on your laptop.</p><p>Then the file grows to 12 million rows. The loop runs out of heap. A network blip on row 8,000,000 kills the process — and you have no idea which rows committed, so you can’t safely re-run it. There’s no progress reporting, no retry, no parallelism, and certainly no way to resume from where it died.</p><p>This is exactly the problem <strong>Spring Batch</strong> was built to solve. It provides a battle-tested framework for chunk-oriented processing with transactions, restartability, skip/retry policies, and partitioned parallelism — the unglamorous machinery that turns a fragile script into a reliable production job. This article builds a complete, real example and covers the production concerns that matter.</p><h3>The core model</h3><p>Spring Batch organizes work into a hierarchy:</p><ul><li>A <strong>Job</strong> is the whole unit of work (e.g. &quot;import customers&quot;).</li><li>A Job has one or more <strong>Step</strong>s, run in sequence (or conditionally).</li><li>A chunk-oriented Step is built from three collaborators:</li><li><strong>ItemReader</strong> — reads one item at a time from a source (file, DB, queue).</li><li><strong>ItemProcessor</strong> — transforms/validates/filters one item (optional).</li><li><strong>ItemWriter</strong> — writes items <em>in batches</em> (a chunk) to a destination.</li><li>The <strong>JobRepository</strong> persists execution metadata to the database, which is what makes restart and monitoring possible.</li><li>A <strong>JobLauncher</strong> starts a job with a set of <strong>JobParameters</strong>.</li></ul><p>The key insight is <strong>chunk-oriented processing</strong>: read N items one by one, process each, then hand the whole chunk of N to the writer inside a single transaction. Commit, repeat. This bounds memory to one chunk and gives you natural transactional boundaries.</p><h3>Project setup</h3><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>    &lt;artifactId&gt;spring-boot-starter-batch&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br>&lt;dependency&gt;<br>    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>    &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br>&lt;dependency&gt;<br>    &lt;groupId&gt;org.postgresql&lt;/groupId&gt;<br>    &lt;artifactId&gt;postgresql&lt;/artifactId&gt;<br>    &lt;scope&gt;runtime&lt;/scope&gt;<br>&lt;/dependency&gt;</pre><p>Spring Batch needs its metadata tables (BATCH_JOB_INSTANCE, BATCH_STEP_EXECUTION, etc.). Spring Boot creates them automatically:</p><pre>spring.batch.jdbc.initialize-schema=always<br># Don&#39;t run jobs automatically on context startup; we&#39;ll launch explicitly<br>spring.batch.job.enabled=false</pre><p>In Spring Boot 3 / Spring Batch 5, @EnableBatchProcessing is no longer required — auto-configuration wires the JobRepository, JobLauncher, and a JDBC transactionManager for you. (Adding @EnableBatchProcessing actually <em>disables</em> that auto-config, so leave it off unless you need to customize.)</p><h3>A complete worked example</h3><p>The job: read a large CSV of raw customer records, validate and normalize them, and persist valid ones to the database.</p><h3>The domain</h3><pre>public record CustomerCsv(String firstName, String lastName, String email, String country) {}</pre><pre>@Entity<br>@Table(name = &quot;customer&quot;)<br>public class Customer {<br>    @Id<br>    @GeneratedValue(strategy = GenerationType.IDENTITY)<br>    private Long id;<br>    private String firstName;<br>    private String lastName;<br>    private String email;<br>    private String countryCode;<br>    // getters / setters<br>}</pre><h3>The reader</h3><p>FlatFileItemReader streams the file line by line — it never loads the whole file into memory.</p><pre>import org.springframework.batch.item.file.FlatFileItemReader;<br>import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;<br>import org.springframework.core.io.FileSystemResource;</pre><pre>@Bean<br>@StepScope<br>public FlatFileItemReader&lt;CustomerCsv&gt; customerReader(<br>        @Value(&quot;#{jobParameters[&#39;inputFile&#39;]}&quot;) String inputFile) {<br>    return new FlatFileItemReaderBuilder&lt;CustomerCsv&gt;()<br>        .name(&quot;customerReader&quot;)<br>        .resource(new FileSystemResource(inputFile))<br>        .linesToSkip(1)                       // header row<br>        .delimited()<br>        .names(&quot;firstName&quot;, &quot;lastName&quot;, &quot;email&quot;, &quot;country&quot;)<br>        .targetType(CustomerCsv.class)<br>        .build();<br>}</pre><p>@StepScope is important: it lets the reader receive a late-bound jobParameters value (the file path) and gives each execution its own reader instance.</p><h3>The processor</h3><p>The processor validates and normalizes. Returning null from a processor <strong>filters</strong> the item — it is silently dropped and never reaches the writer.</p><pre>import org.springframework.batch.item.ItemProcessor;<br>import org.springframework.stereotype.Component;</pre><pre>@Component<br>public class CustomerProcessor implements ItemProcessor&lt;CustomerCsv, Customer&gt; {</pre><pre>    private static final java.util.regex.Pattern EMAIL =<br>        java.util.regex.Pattern.compile(&quot;^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$&quot;);</pre><pre>    @Override<br>    public Customer process(CustomerCsv in) {<br>        if (in.email() == null || !EMAIL.matcher(in.email().trim()).matches()) {<br>            return null; // filter out invalid rows<br>        }<br>        Customer c = new Customer();<br>        c.setFirstName(in.firstName().trim());<br>        c.setLastName(in.lastName().trim());<br>        c.setEmail(in.email().trim().toLowerCase());<br>        c.setCountryCode(normalizeCountry(in.country()));<br>        return c;<br>    }</pre><pre>    private String normalizeCountry(String raw) {<br>        if (raw == null) return &quot;XX&quot;;<br>        return raw.trim().toUpperCase().substring(0, Math.min(2, raw.trim().length()));<br>    }<br>}</pre><h3>The writer</h3><p>JpaItemWriter persists a whole chunk in one transaction.</p><pre>import org.springframework.batch.item.data.JpaItemWriter;<br>import org.springframework.batch.item.data.builder.JpaItemWriterBuilder;<br>import jakarta.persistence.EntityManagerFactory;</pre><pre>@Bean<br>public JpaItemWriter&lt;Customer&gt; customerWriter(EntityManagerFactory emf) {<br>    return new JpaItemWriterBuilder&lt;Customer&gt;()<br>        .entityManagerFactory(emf)<br>        .build();<br>}</pre><p>For Raw throughput, JdbcBatchItemWriter with a parameterized INSERT is faster because it bypasses the persistence context:</p><pre>@Bean<br>public JdbcBatchItemWriter&lt;Customer&gt; jdbcCustomerWriter(DataSource dataSource) {<br>    return new JdbcBatchItemWriterBuilder&lt;Customer&gt;()<br>        .dataSource(dataSource)<br>        .sql(&quot;&quot;&quot;<br>            insert into customer (first_name, last_name, email, country_code)<br>            values (:firstName, :lastName, :email, :countryCode)<br>            &quot;&quot;&quot;)<br>        .beanMapped()<br>        .build();<br>}</pre><h3>Wiring the Step and Job</h3><p>In Spring Batch 5 you build steps and jobs with builders that take the JobRepository and a transaction manager.</p><pre>import org.springframework.batch.core.Job;<br>import org.springframework.batch.core.Step;<br>import org.springframework.batch.core.job.builder.JobBuilder;<br>import org.springframework.batch.core.repository.JobRepository;<br>import org.springframework.batch.core.step.builder.StepBuilder;<br>import org.springframework.transaction.PlatformTransactionManager;<br>import org.springframework.context.annotation.Bean;<br>import org.springframework.context.annotation.Configuration;</pre><pre>@Configuration<br>public class CustomerImportJobConfig {</pre><pre>    @Bean<br>    public Step importStep(JobRepository jobRepository,<br>                           PlatformTransactionManager txManager,<br>                           FlatFileItemReader&lt;CustomerCsv&gt; customerReader,<br>                           CustomerProcessor processor,<br>                           JpaItemWriter&lt;Customer&gt; customerWriter) {<br>        return new StepBuilder(&quot;importStep&quot;, jobRepository)<br>            .&lt;CustomerCsv, Customer&gt;chunk(1000, txManager)<br>            .reader(customerReader)<br>            .processor(processor)<br>            .writer(customerWriter)<br>            .build();<br>    }</pre><pre>    @Bean<br>    public Job importCustomersJob(JobRepository jobRepository, Step importStep) {<br>        return new JobBuilder(&quot;importCustomersJob&quot;, jobRepository)<br>            .start(importStep)<br>            .build();<br>    }<br>}</pre><p>chunk(1000, txManager) means: read up to 1,000 items, process them, write all 1,000, and commit — then start the next chunk. Pick a chunk size that balances transaction overhead against memory and lock duration. 500–2,000 is a sensible starting range; benchmark for your workload.</p><h3>Launching the job</h3><pre>import org.springframework.batch.core.*;<br>import org.springframework.batch.core.launch.JobLauncher;</pre><pre>@Component<br>public class CustomerImportRunner {</pre><pre>    private final JobLauncher jobLauncher;<br>    private final Job importCustomersJob;</pre><pre>    public CustomerImportRunner(JobLauncher jobLauncher, Job importCustomersJob) {<br>        this.jobLauncher = jobLauncher;<br>        this.importCustomersJob = importCustomersJob;<br>    }</pre><pre>    public void run(String inputFile) throws Exception {<br>        JobParameters params = new JobParametersBuilder()<br>            .addString(&quot;inputFile&quot;, inputFile)<br>            .addLong(&quot;run.timestamp&quot;, System.currentTimeMillis()) // makes the instance unique<br>            .toJobParameters();<br>        jobLauncher.run(importCustomersJob, params);<br>    }<br>}</pre><h3>Restartability</h3><p>This is Spring Batch’s superpower. Because the JobRepository persists step execution state — including the read count — a job that <em>failed</em> can be re-launched <strong>with the same identifying job parameters</strong> and it resumes from the last committed chunk rather than starting over.</p><p>A few rules govern this:</p><ul><li>A <strong>JobInstance</strong> is identified by job name + <em>identifying</em> job parameters. Re-running with the same identifying parameters continues that instance; changing them creates a new instance.</li><li>That’s why above we add a run.timestamp — a <em>unique</em> parameter makes each launch a fresh instance. For a <em>restartable</em> job you&#39;d instead keep parameters identical (e.g. just inputFile) so a failed run can resume.</li><li>A COMPLEQED instance cannot be re-run with the same parameters (you&#39;d get JobInstanceAlreadyCompleteException). Only FAILED/STOPPED instances restart.</li><li>Readers like FlatFileItemReader are <strong>restartable</strong>: they persist the line offset in the ExecutionContext and skip already-read lines on restart.</li></ul><p>You can also mark a step non-restartable or allow a completed step to re-run with .allowStartIfComplete(true).</p><h3>Fault tolerance: skip and retry</h3><p>In the real world some records are simply bad and some failures are transient. A fault-tolerant step lets you handle both without aborting the whole job.</p><pre>@Bean<br>public Step faultTolerantStep(JobRepository jobRepository,<br>                              PlatformTransactionManager txManager,<br>                              FlatFileItemReader&lt;CustomerCsv&gt; reader,<br>                              CustomerProcessor processor,<br>                              JpaItemWriter&lt;Customer&gt; writer) {<br>    return new StepBuilder(&quot;faultTolerantStep&quot;, jobRepository)<br>        .&lt;CustomerCsv, Customer&gt;chunk(1000, txManager)<br>        .reader(reader)<br>        .processor(processor)<br>        .writer(writer)<br>        .faultTolerant()<br>        // retry transient failures up to 3 times<br>        .retryLimit(3)<br>        .retry(org.springframework.dao.TransientDataAccessException.class)<br>        // skip bad records, up to 50 of them, then fail the job<br>        .skipLimit(50)<br>        .skip(org.springframework.batch.item.file.FlatFileParseException.class)<br>        .skip(jakarta.validation.ConstraintViolationException.class)<br>        .listener(skipListener())<br>        .build();<br>}</pre><ul><li><strong>Retry</strong> re-attempts an operation that threw a <em>transient</em> exception (a deadlock, a momentary connection drop). Use it only for errors that might succeed on a second try.</li><li><strong>Skip</strong> discards an <em>individual bad item</em> (a malformed CSV line, a validation failure) and continues, up to skipLimit. Exceed the limit and the job fails — that&#39;s a safety valve against silently dropping a corrupt file.</li><li>Capture skips so they aren’t lost:</li></ul><pre>@Bean<br>public org.springframework.batch.core.SkipListener&lt;CustomerCsv, Customer&gt; skipListener() {<br>    return new org.springframework.batch.core.SkipListener&lt;&gt;() {<br>        @Override<br>        public void onSkipInRead(Throwable t) {<br>            // log/persist the unparseable line for later inspection<br>        }<br>        @Override<br>        public void onSkipInProcess(CustomerCsv item, Throwable t) {<br>            // record rejected item to a dead-letter table<br>        }<br>        @Override<br>        public void onSkipInWrite(Customer item, Throwable t) {<br>            // record write failure<br>        }<br>    };<br>}</pre><p><strong>Important caveat:</strong> when a fault-tolerant chunk hits an error during <em>write</em>, Spring Batch rolls back the chunk and re-processes it <strong>item by item</strong> to isolate the offender. This means your ItemProcessor must be <strong>idempotent</strong> — it may be called more than once for the same item.</p><h3>Partitioning for scale</h3><p>A single thread reading and writing in chunks is reliable but sequential. For millions of rows you want parallelism. <strong>Partitioning</strong> splits the input into ranges and runs a <em>separate worker step</em> per partition, each with its own reader/writer, in parallel across a thread pool.</p><pre>import org.springframework.batch.core.partition.support.Partitioner;<br>import org.springframework.batch.item.ExecutionContext;<br>import java.util.HashMap;<br>import java.util.Map;</pre><pre>public class IdRangePartitioner implements Partitioner {</pre><pre>    private final long minId;<br>    private final long maxId;</pre><pre>    public IdRangePartitioner(long minId, long maxId) {<br>        this.minId = minId;<br>        this.maxId = maxId;<br>    }</pre><pre>    @Override<br>    public Map&lt;String, ExecutionContext&gt; partition(int gridSize) {<br>        Map&lt;String, ExecutionContext&gt; partitions = new HashMap&lt;&gt;();<br>        long range = (maxId - minId) / gridSize + 1;<br>        long start = minId;<br>        for (int i = 0; i &lt; gridSize; i++) {<br>            ExecutionContext ctx = new ExecutionContext();<br>            long end = Math.min(start + range - 1, maxId);<br>            ctx.putLong(&quot;minId&quot;, start);<br>            ctx.putLong(&quot;maxId&quot;, end);<br>            partitions.put(&quot;partition&quot; + i, ctx);<br>            start = end + 1;<br>        }<br>        return partitions;<br>    }<br>}</pre><p>Wire a manager step that distributes partitions to a worker step running on a task executor:</p><pre>@Bean<br>public Step partitionedStep(JobRepository jobRepository,<br>                            Step workerStep) {<br>    return new StepBuilder(&quot;partitionedStep&quot;, jobRepository)<br>        .partitioner(workerStep.getName(),<br>                     new IdRangePartitioner(1, 12_000_000))<br>        .step(workerStep)<br>        .gridSize(8)<br>        .taskExecutor(new org.springframework.core.task.SimpleAsyncTaskExecutor())<br>        .build();<br>}</pre><p>The worker step’s reader uses the partition’s minId/maxId from the step ExecutionContext (inject via @Value(&quot;#{stepExecutionContext[&#39;minId&#39;]}&quot;) on a @StepScope bean). For a DB source, a JdbcPagingItemReader with a where id between :minId and :maxId clause keeps each partition&#39;s reads bounded and indexed.</p><p>Spring Batch also supports <strong>remote partitioning</strong> (workers on separate JVMs, coordinated via a message queue) for horizontal scale beyond one machine. Local partitioning across threads is the simplest big win.</p><h3>Listeners and the ExecutionContext</h3><p>Real jobs need hooks for logging, metrics, cleanup, and passing data between steps. Spring Batch provides listeners at every level.</p><pre>import org.springframework.batch.core.JobExecution;<br>import org.springframework.batch.core.JobExecutionListener;</pre><pre>public class ImportJobListener implements JobExecutionListener {</pre><pre>    @Override<br>    public void beforeJob(JobExecution jobExecution) {<br>        // record start time, validate inputs, send a &quot;started&quot; notification<br>    }</pre><pre>    @Override<br>    public void afterJob(JobExecution jobExecution) {<br>        long written = jobExecution.getStepExecutions().stream()<br>            .mapToLong(se -&gt; se.getWriteCount()).sum();<br>        long skipped = jobExecution.getStepExecutions().stream()<br>            .mapToLong(se -&gt; se.getSkipCount()).sum();<br>        // emit metrics, alert if skipped is high, archive the input file<br>    }<br>}</pre><p>Attach it with .listener(new ImportJobListener()) on the JobBuilder. There are matching StepExecutionListener, ChunkListener, and ItemReadListener interfaces for finer-grained hooks.</p><p>To pass data between steps — say, a step that computes an ID range and a later step that consumes it — write to the <strong>job </strong><strong>ExecutionContext</strong> (shared across the job), not the step context (scoped to one step). The execution context is persisted by the JobRepository, so its contents also survive a restart, which is exactly what makes resumable multi-step jobs possible.</p><h3>Composing multiple steps</h3><p>Jobs rarely have one step. You might <strong>stage</strong> raw data, <strong>validate</strong> it, then <strong>load</strong> it. The JobBuilder flow API sequences and branches steps:</p><pre>@Bean<br>public Job pipelineJob(JobRepository repo, Step stageStep,<br>                       Step validateStep, Step loadStep, Step cleanupStep) {<br>    return new JobBuilder(&quot;pipelineJob&quot;, repo)<br>        .start(stageStep)<br>        .next(validateStep)<br>        .on(&quot;FAILED&quot;).to(cleanupStep)          // branch on exit status<br>        .from(validateStep).on(&quot;*&quot;).to(loadStep)<br>        .end()<br>        .build();<br>}</pre><p>If a step fails, the persisted state lets you restart the <em>whole job</em> and it resumes at the failed step — completed steps are skipped (unless marked allowStartIfComplete). This makes long ETL pipelines robust: a failure in the load step doesn&#39;t force you to re-stage gigabytes of data.</p><h3>Tuning and monitoring</h3><pre>| Lever | Effect | Guidance |<br>|-------|--------|----------|<br>| Chunk size | Transaction frequency vs memory | Start 500–2,000; benchmark |<br>| Partition grid size | Parallelism | Match to CPU cores / DB connection pool |<br>| `JdbcBatchItemWriter` | Throughput | Faster than JPA for pure inserts |<br>| Connection pool size | Concurrency ceiling | Pool ≥ grid size + headroom |<br>| `default_batch_fetch_size` | Reader efficiency | Reduce N+1 if reader touches associations |</pre><p>Monitor via the BATCH_STEP_EXECUTION table (read/write/skip/commit counts) or expose Micrometer metrics — Spring Batch publishes spring.batch.job and spring.batch.step timers automatically when Actuator is present.</p><h3>Common pitfalls</h3><ul><li><strong>Loading everything into memory.</strong> Use streaming readers (FlatFileItemReader, JdbcCursorItemReader, JdbcPagingItemReader), never findAll() into a List.</li><li><strong>Non-idempotent processors.</strong> Fault-tolerant retries re-invoke process(). Side effects must be safe to repeat.</li><li><strong>Reusing identifying job parameters on a completed job.</strong> Causes JobInstanceAlreadyCompleteException. Add a unique parameter for genuinely new runs; keep them identical only when you intend to restart.</li><li><strong>Skipping silently.</strong> A high skipLimit with no SkipListener quietly discards data. Always record skipped items to a dead-letter store and alert on volume.</li><li><strong>Huge chunk inside a single transaction.</strong> A 100,000-item chunk holds locks and a giant transaction; a failure rolls back everything. Keep chunks modest.</li><li><strong>@EnableBatchProcessing in Boot 3.</strong> It disables the auto-configuration you actually want. Omit it unless customizing.</li><li><strong>spring.batch.job.enabled=true running jobs on every startup.</strong> Disable auto-run and launch explicitly so you control parameters and timing.</li><li><strong>Partitioning without enough DB connections.</strong> Eight parallel workers need eight connections; size the pool accordingly or workers will block.</li></ul><h3>Wrapping up</h3><p>Spring Batch is not the flashiest part of the Spring ecosystem, but it’s one of the most valuable. It turns “read a file and insert rows” into a job that streams within bounded memory, commits in transactional chunks, resumes after failure, skips bad records into a dead-letter store, retries transient errors, and scales out across partitions.</p><p>Build with ItemReader → ItemProcessor → ItemWriter, choose a sane chunk size, make your processor idempotent, add fault tolerance deliberately, and partition when you need throughput. Your 12-million-row import becomes boring — which, for a batch job, is exactly what you want.</p><p>If this leveled up your batch game, <strong>follow the devdomain publication</strong> for more deep Spring engineering. What’s the largest job you’ve run, and how did you tune it? Share your numbers in the <strong>comments</strong>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5c86937152c1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devdomain/spring-batch-processing-millions-of-records-reliablyit-always-starts-simple-5c86937152c1">Spring Batch: Processing Millions of Records ReliablyIt always starts simple.</a> was originally published in <a href="https://medium.com/devdomain">devdomain</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[em vs rem Explained ]]></title>
            <link>https://medium.com/the-60-second-programmer/em-vs-rem-explained-8234797a5193?source=rss-f417e4e45a5e------2</link>
            <guid isPermaLink="false">https://medium.com/p/8234797a5193</guid>
            <category><![CDATA[remote-work]]></category>
            <category><![CDATA[accessibility]]></category>
            <category><![CDATA[me]]></category>
            <category><![CDATA[css]]></category>
            <dc:creator><![CDATA[Marcelo Domingues]]></dc:creator>
            <pubDate>Mon, 26 Oct 2026 03:41:01 GMT</pubDate>
            <atom:updated>2026-10-26T03:41:01.516Z</atom:updated>
            <content:encoded><![CDATA[<h4>Two relative units — one inherits, one stays fixed to the root.</h4><p>Both are relative font-size units, but they measure against different things — and that difference matters.</p><h3>em → relative to the parent</h3><p>1em equals the <strong>parent element&#39;s</strong> font size. So em values compound as you nest.</p><pre>.parent { font-size: 20px; }<br>.child  { font-size: 1.5em; } /* 30px */<br>.grandchild { font-size: 1.5em; } /* 45px — compounds! */</pre><p>That cascading can surprise you when components nest deeply.</p><h3>rem → relative to the root</h3><p>1rem always equals the <strong>root</strong> (&lt;html&gt;) font size, no matter how deep you are. No compounding.</p><pre>html { font-size: 16px; }<br>.title { font-size: 2rem;  } /* always 32px */<br>.body  { font-size: 1rem;  } /* always 16px */</pre><h3>When to use which</h3><p>rem for font sizes and most layout — predictable and accessibility-friendly (it respects the user&#39;s browser font setting). em for spacing that <em>should</em> scale with its own text, like button padding:</p><pre>.btn {<br>  font-size: 1.25rem;<br>  padding: 0.5em 1em; /* scales with the button&#39;s text */<br>}</pre><p><strong>TL;DR:</strong> Use rem for consistent, accessible sizing; reach for em when you want a value to scale with its local font size.</p><p>Set padding in em on a button, then bump its font-size — the padding scales right along with it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8234797a5193" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-60-second-programmer/em-vs-rem-explained-8234797a5193">em vs rem Explained 📏</a> was originally published in <a href="https://medium.com/the-60-second-programmer">The 60-Second Programmer</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>