<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged microservices</title>
<link>https://coderlegion.com/tag/microservices</link>
<description>Powered by Question2Answer</description>
<item>
<title>Reliable Event-Driven Architecture in Spring Boot: Outbox, Inbox, Retries, and Idempotency</title>
<link>https://coderlegion.com/32907/reliable-event-driven-architecture-in-spring-boot-outbox-inbox-retries-and-idempotency</link>
<description>&lt;p&gt;&lt;em&gt;Why publishing an event is only the beginning of building a reliable distributed system.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Building an event-driven application with Spring Boot is relatively straightforward.&lt;/p&gt;
&lt;p&gt;You create a Kafka producer, publish an event, implement a consumer, and process the message.&lt;/p&gt;
&lt;p&gt;Everything works beautifully during development.&lt;/p&gt;
&lt;p&gt;Then production introduces the situations your happy-path tests never covered.&lt;/p&gt;
&lt;p&gt;What happens when the database transaction commits, but Kafka is unavailable? What happens when the consumer processes an event successfully but crashes before acknowledging it? What happens when two application instances attempt to process the same message?&lt;/p&gt;
&lt;p&gt;These aren't unusual edge cases. They're fundamental challenges of distributed systems.&lt;/p&gt;
&lt;p&gt;And solving them requires more than adding a retry annotation.&lt;/p&gt;
&lt;p&gt;Let's explore the reliability boundaries behind a production-ready event-driven architecture.&lt;/p&gt;
&lt;h3 id=&quot;1-the-dual-write-problem&quot;&gt;1. The dual-write problem&lt;/h3&gt;
&lt;p&gt;Imagine a payment service that must perform two operations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Save a payment in PostgreSQL.&lt;/li&gt;
&lt;li&gt;Publish a &lt;code&gt;PaymentCompleted&lt;/code&gt; event to Kafka.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A straightforward implementation might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void completePayment(Payment payment) {
    paymentRepository.save(payment);
    kafkaTemplate.send(
        &quot;payment.completed&quot;,
        new PaymentCompleted(payment.getId())
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code looks reasonable, but there's a serious problem.&lt;/p&gt;
&lt;p&gt;The database transaction and Kafka publication do not share a single atomic transaction.&lt;/p&gt;
&lt;p&gt;Several outcomes are possible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The database commits, but the event is never published.&lt;/li&gt;
&lt;li&gt;Kafka accepts the event, but the database transaction rolls back.&lt;/li&gt;
&lt;li&gt;Publication times out, leaving the application uncertain whether Kafka received the event.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Adding retries doesn't eliminate the fundamental inconsistency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The problem is not whether Kafka is reliable. The problem is coordinating two independent systems.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;the-transactional-outbox&quot;&gt;The transactional outbox&lt;/h4&gt;
&lt;p&gt;The transactional outbox pattern changes the transaction boundary.&lt;/p&gt;
&lt;p&gt;Instead of publishing directly to Kafka, the application writes an outbox record within the same database transaction as the business operation.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Payment Service
      |
      v
Database Transaction
      |
      +-- Save Payment
      |
      +-- Save Outbox Event
      |
      v
    Commit
      |
      v
Outbox Publisher
      |
      v
    Kafka
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Either the payment and outbox event are committed together, or neither is committed.&lt;/p&gt;
&lt;p&gt;A separate publisher retrieves pending outbox records and delivers them to Kafka.&lt;/p&gt;
&lt;p&gt;This solves the atomicity problem between the business state and the durable intent to publish.&lt;/p&gt;
&lt;p&gt;It does not guarantee that an event will be published exactly once.&lt;/p&gt;
&lt;p&gt;That distinction matters.&lt;/p&gt;
&lt;h3 id=&quot;2-why-the-outbox-still-needs-idempotency&quot;&gt;2. Why the outbox still needs idempotency&lt;/h3&gt;
&lt;p&gt;Consider this sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The outbox publisher sends an event to Kafka.&lt;/li&gt;
&lt;li&gt;Kafka successfully accepts it.&lt;/li&gt;
&lt;li&gt;The application crashes before marking the outbox record as published.&lt;/li&gt;
&lt;li&gt;The publisher restarts and sends the event again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Kafka may now contain two copies of the same logical event.&lt;/p&gt;
&lt;p&gt;This is a normal consequence of at-least-once delivery.&lt;/p&gt;
&lt;p&gt;The correct response is not to assume duplicates can always be prevented.&lt;/p&gt;
&lt;p&gt;Instead, design consumers that can safely tolerate them.&lt;/p&gt;
&lt;h3 id=&quot;3-the-transactional-inbox&quot;&gt;3. The transactional inbox&lt;/h3&gt;
&lt;p&gt;An inbox provides a durable record of incoming events that have already been processed.&lt;/p&gt;
&lt;p&gt;Each event should carry a stable identifier, such as an event ID generated when the logical event is created.&lt;/p&gt;
&lt;p&gt;When the consumer receives a message, it checks whether that event has already been processed.&lt;/p&gt;
&lt;p&gt;A simplified model looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Kafka Event
     |
     v
Consumer
     |
     v
Database Transaction
     |
     +-- Claim Event ID in Inbox
     |
     +-- Apply Business Changes
     |
     +-- Mark Inbox Completed
     |
     v
   Commit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part is the transaction boundary.&lt;/p&gt;
&lt;p&gt;The inbox record and the business changes must participate in the same transaction when using the same transactional database.&lt;/p&gt;
&lt;p&gt;Otherwise, a consumer can record an event as completed even though its business operation failed.&lt;/p&gt;
&lt;h4 id=&quot;a-subtle-but-important-case&quot;&gt;A subtle but important case&lt;/h4&gt;
&lt;p&gt;Suppose a consumer receives &lt;code&gt;PaymentCompleted&lt;/code&gt;, updates an invoice, and produces &lt;code&gt;InvoicePaid&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now the reliability boundary becomes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inbox + Business State + Outbox&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All three should be committed atomically.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Incoming Event
      |
      v
Database Transaction
      |
      +-- Inbox Claim
      |
      +-- Update Invoice
      |
      +-- Create Outbox Event
      |
      v
    Commit
      |
      v
Outbox Publisher
      |
      v
Outgoing Event
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is one of the most useful patterns for building dependable event-driven workflows.&lt;/p&gt;
&lt;p&gt;The inbox protects the incoming side. The outbox protects the outgoing side.&lt;/p&gt;
&lt;p&gt;Neither eliminates the need for careful business-level idempotency, especially when external side effects are involved.&lt;/p&gt;
&lt;h3 id=&quot;4-retries-are-a-lifecycle-not-just-an-annotation&quot;&gt;4. Retries are a lifecycle, not just an annotation&lt;/h3&gt;
&lt;p&gt;Retries are often introduced with a simple configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Retryable(
    retryFor = TransientException.class,
    maxAttempts = 3
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For short-lived operations, that can be perfectly reasonable.&lt;/p&gt;
&lt;p&gt;But what happens when a dependency is unavailable for 30 minutes? What happens when the application restarts? What happens when an event requires operator intervention?&lt;/p&gt;
&lt;p&gt;An in-memory retry loop cannot reliably manage every long-running failure scenario.&lt;/p&gt;
&lt;p&gt;For durable asynchronous processing, retries should be modeled as persistent state.&lt;/p&gt;
&lt;p&gt;A typical lifecycle might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;PENDING
   |
   v
PROCESSING
   |
   +---- Success ----&amp;gt; COMPLETED
   |
   +---- Retryable failure
   |           |
   |           v
   |      RETRY_SCHEDULED
   |           |
   |           v
   |        PENDING
   |
   +---- Terminal failure
               |
               v
             FAILED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each attempt can record useful information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Attempt count&lt;/li&gt;
&lt;li&gt;Next eligible execution time&lt;/li&gt;
&lt;li&gt;Last failure category&lt;/li&gt;
&lt;li&gt;Processing status&lt;/li&gt;
&lt;li&gt;Relevant timestamps&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Exponential backoff with jitter helps avoid repeatedly overwhelming an unhealthy dependency.&lt;/p&gt;
&lt;p&gt;But retry policies should distinguish transient failures from permanent ones.&lt;/p&gt;
&lt;p&gt;Retrying an invalid business request a thousand times won't make it valid.&lt;/p&gt;
&lt;h3 id=&quot;5-multi-instance-processing-changes-everything&quot;&gt;5. Multi-instance processing changes everything&lt;/h3&gt;
&lt;p&gt;A background publisher may work perfectly when there's only one application instance.&lt;/p&gt;
&lt;p&gt;Then the service is scaled to three pods.&lt;/p&gt;
&lt;p&gt;All three publishers query the same pending records.&lt;/p&gt;
&lt;p&gt;Without coordination, multiple instances may attempt to publish or process the same work concurrently.&lt;/p&gt;
&lt;p&gt;One common PostgreSQL technique is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT id
FROM outbox_event
WHERE status = 'PENDING'
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 100;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SKIP LOCKED&lt;/code&gt; allows concurrent workers to skip rows already locked by another transaction.&lt;/p&gt;
&lt;p&gt;This can improve coordination between workers, but it's not a complete delivery protocol.&lt;/p&gt;
&lt;p&gt;The design must still consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How work is claimed and how long claims remain valid&lt;/li&gt;
&lt;li&gt;Transaction duration and lock ownership&lt;/li&gt;
&lt;li&gt;Recovery after a worker crashes&lt;/li&gt;
&lt;li&gt;Retry scheduling and fairness&lt;/li&gt;
&lt;li&gt;Ordering requirements&lt;/li&gt;
&lt;li&gt;Duplicate delivery&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, holding database locks across a slow network call can increase contention and create unnecessarily long transactions.&lt;/p&gt;
&lt;p&gt;A robust implementation may use short claim transactions, explicit processing ownership, and recovery mechanisms rather than keeping database transactions open while publishing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Concurrency control and delivery guarantees are related, but they are not interchangeable.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;6-exactly-once-is-not-an-end-to-end-promise&quot;&gt;6. Exactly-once is not an end-to-end promise&lt;/h3&gt;
&lt;p&gt;It's tempting to describe an event-driven system as exactly-once.&lt;/p&gt;
&lt;p&gt;But exactly-once semantics depend on the boundaries being discussed.&lt;/p&gt;
&lt;p&gt;Kafka transactions can provide guarantees for supported Kafka operations. A PostgreSQL transaction can atomically update records within PostgreSQL.&lt;/p&gt;
&lt;p&gt;Neither automatically makes a database update, Kafka publication, HTTP request, and external payment operation one globally atomic action.&lt;/p&gt;
&lt;p&gt;A more useful engineering goal is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At-least-once delivery with effectively-once business outcomes wherever possible.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That typically requires durable event identities, transactional inbox processing, idempotent business operations, and explicit handling of external side effects.&lt;/p&gt;
&lt;p&gt;For payment systems, this distinction is particularly important.&lt;/p&gt;
&lt;p&gt;Processing the same event twice must not mean charging a customer twice.&lt;/p&gt;
&lt;h3 id=&quot;7-observability-is-part-of-reliability&quot;&gt;7. Observability is part of reliability&lt;/h3&gt;
&lt;p&gt;A system that retries failures silently can appear healthy while accumulating thousands of unprocessed events.&lt;/p&gt;
&lt;p&gt;Operational visibility should answer questions such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How many events are pending?&lt;/li&gt;
&lt;li&gt;How old is the oldest unpublished event?&lt;/li&gt;
&lt;li&gt;How many retries are scheduled?&lt;/li&gt;
&lt;li&gt;Which events are permanently failing?&lt;/li&gt;
&lt;li&gt;Are workers making progress?&lt;/li&gt;
&lt;li&gt;Are duplicates being detected?&lt;/li&gt;
&lt;li&gt;Is event processing latency increasing?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Metrics, structured logs, tracing, and operator-facing failure information are not optional extras for a production system.&lt;/p&gt;
&lt;p&gt;They help distinguish a temporary delay from a workflow that has stopped making progress.&lt;/p&gt;
&lt;p&gt;A reliable system should make failure visible and recovery understandable.&lt;/p&gt;
&lt;h3 id=&quot;8-putting-these-patterns-together&quot;&gt;8. Putting these patterns together&lt;/h3&gt;
&lt;p&gt;A practical architecture combines several responsibilities:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Business Operation
       |
       v
Transactional Outbox
       |
       v
Durable Publisher
       |
       v
Message Broker
       |
       v
Transactional Inbox
       |
       v
Business Processing
       |
       +-- State Changes
       |
       +-- Optional New Outbox Events
       |
       v
Commit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Around that flow, we need retry policies, concurrency coordination, idempotency, recovery, and observability.&lt;/p&gt;
&lt;p&gt;Each component solves a different failure mode.&lt;/p&gt;
&lt;p&gt;The outbox prevents losing the intent to publish when business data commits.&lt;/p&gt;
&lt;p&gt;The inbox protects consumers from repeated processing.&lt;/p&gt;
&lt;p&gt;Retries help transient failures recover.&lt;/p&gt;
&lt;p&gt;Concurrency controls coordinate multiple workers.&lt;/p&gt;
&lt;p&gt;Observability makes the system operable.&lt;/p&gt;
&lt;p&gt;None of these patterns should be treated as magic.&lt;/p&gt;
&lt;p&gt;Their value comes from understanding exactly which failure boundary each one protects.&lt;/p&gt;
&lt;h3 id=&quot;9-why-i-built-nerv-event&quot;&gt;9. Why I built NERV Event&lt;/h3&gt;
&lt;p&gt;After working with event-driven enterprise applications, I wanted these reliability concerns to be reusable rather than repeatedly implemented in individual services.&lt;/p&gt;
&lt;p&gt;That led to &lt;strong&gt;NERV Event&lt;/strong&gt;, an open-source Java and Spring Boot library focused on patterns such as transactional outbox, inbox processing, retries, and idempotency.&lt;/p&gt;
&lt;p&gt;The goal isn't to hide distributed-systems complexity behind annotations.&lt;/p&gt;
&lt;p&gt;It's to provide reusable infrastructure while keeping the reliability model explicit.&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/czetsuyatech/nerv-event&quot;&gt;https://github.com/czetsuyatech/nerv-event&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The library is one possible implementation of these ideas. The architectural principles are useful regardless of which framework or library you choose.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;Publishing an event is easy.&lt;/p&gt;
&lt;p&gt;Building a system that behaves predictably when databases, brokers, application instances, and downstream dependencies fail is much harder.&lt;/p&gt;
&lt;p&gt;Reliable event-driven architecture begins with a simple question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What happens if the application crashes at this exact point?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ask that question at every transaction boundary, every message acknowledgment, and every external call.&lt;/p&gt;
&lt;p&gt;The answers will reveal where durability, idempotency, retries, and recovery are actually needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I'd be interested to hear how other teams approach this:&lt;/strong&gt; Do you use a transactional inbox alongside your outbox, or do you handle consumer idempotency directly in your business tables?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.czetsuyatech.com&quot;&gt;CzetsuyaTech&lt;/a&gt;. Explore more articles on Java, Spring Boot, distributed systems, and software architecture there.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32907/reliable-event-driven-architecture-in-spring-boot-outbox-inbox-retries-and-idempotency</guid>
<pubDate>Thu, 29 Oct 2026 00:07:44 +0000</pubDate>
</item>
<item>
<title>Polar Bear Cache: Local Caching Without Repeated Remote Calls</title>
<link>https://coderlegion.com/32489/polar-bear-cache-local-caching-without-repeated-remote-calls</link>
<description>&lt;p&gt;&lt;strong&gt;Shared storage provides the source of truth. Local caches provide speed. Events help keep cache state in sync.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; Polar Bear Cache — &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/babyblue94520/polar-bear-cache&quot;&gt;GitHub repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In my previous discussions about microservices, I explored how shared storage and event-driven synchronization can work together to manage distributed session state.&lt;/p&gt;
&lt;p&gt;This time, I want to focus on another part of the problem: caching frequently accessed data across multiple service instances.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-faster-reads-distributed-cache-invalidation&quot;&gt;The Problem: Faster Reads, Distributed Cache Invalidation&lt;/h3&gt;
&lt;p&gt;In a microservices architecture, multiple service instances often need the same data, especially data that is read frequently but updated relatively infrequently.&lt;/p&gt;
&lt;p&gt;A distributed cache can reduce database load, but accessing it still involves network round trips and potentially serialization and deserialization overhead.&lt;/p&gt;
&lt;p&gt;So, what if each service instance could keep frequently accessed data in its own local cache, while receiving notifications when that data changes elsewhere?&lt;/p&gt;
&lt;p&gt;That is the idea behind &lt;strong&gt;Polar Bear Cache&lt;/strong&gt;, an open-source Java caching library designed to work with Spring's familiar cache annotations.&lt;/p&gt;
&lt;h3 id=&quot;the-design-local-cache-event-driven-invalidation&quot;&gt;The Design: Local Cache + Event-Driven Invalidation&lt;/h3&gt;
&lt;p&gt;Each service instance maintains its own local cache. When data changes, an event service can notify other instances that specific cache entries need to be invalidated.&lt;/p&gt;
&lt;p&gt;The key principle is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Broadcast invalidation events, not the entire cached data.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Polar Bear Cache integrates with Spring's cache annotations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Cacheable&lt;/code&gt; — Returns a cached value when available, or executes the underlying method to load it when the value is missing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@CachePut&lt;/code&gt; — Updates the local cache and, when event-service integration is configured, notifies other instances to invalidate the corresponding entry.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@CacheEvict&lt;/code&gt; — Evicts cached data locally and, when event-service integration is configured, notifies other instances to invalidate their corresponding entries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, when a product is updated:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The application updates the source of truth and its local cache.&lt;/li&gt;
&lt;li&gt;It publishes a cache-invalidation event through the event service.&lt;/li&gt;
&lt;li&gt;Other service instances receive the event and evict the corresponding local entry.&lt;/li&gt;
&lt;li&gt;Their next read loads the data again from the source of truth.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This allows repeated reads to stay local in the common case, while cross-instance communication is used to coordinate invalidation when data changes.&lt;/p&gt;
&lt;h3 id=&quot;when-does-this-approach-make-sense-&quot;&gt;When Does This Approach Make Sense?&lt;/h3&gt;
&lt;p&gt;This design is particularly suitable for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read-heavy workloads with relatively infrequent updates.&lt;/li&gt;
&lt;li&gt;Applications running multiple service instances, each with its own local cache.&lt;/li&gt;
&lt;li&gt;Use cases where brief delays in cache invalidation are acceptable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is an important trade-off: event-driven invalidation is asynchronous. Between a data update and the time other instances process the invalidation event, stale data may still be served.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Polar Bear Cache does not guarantee strong consistency across service instances.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Depending on the application, you may also need to consider event-delivery reliability, duplicate events, cache expiration, and recovery strategies when a service instance misses an event.&lt;/p&gt;
&lt;p&gt;The goal is not to eliminate every consistency trade-off, but to make local caching practical in distributed applications without requiring every read to go through a remote cache.&lt;/p&gt;
&lt;h3 id=&quot;why-polar-bear-&quot;&gt;Why “Polar Bear”?&lt;/h3&gt;
&lt;p&gt;The name comes from a somewhat optimistic engineering idea:&lt;/p&gt;
&lt;p&gt;Local caching&lt;br&gt;
→ Fewer remote cache requests and network round trips&lt;br&gt;
→ Potentially more throughput from the same resources&lt;br&gt;
→ Potentially fewer servers needed for a given workload&lt;br&gt;
→ Lower energy consumption and greenhouse gas emissions&lt;br&gt;
→ Maybe we can help save polar bears. 🐻❄️🧊&lt;/p&gt;
&lt;p&gt;Of course, the actual impact depends on workload characteristics, infrastructure, and deployment choices. Performance improvements do not automatically translate into lower energy consumption, but I like the idea that better resource efficiency could benefit both applications and the environment.&lt;/p&gt;
&lt;h3 id=&quot;i-d-love-to-hear-your-thoughts&quot;&gt;I'd Love to Hear Your Thoughts&lt;/h3&gt;
&lt;p&gt;I'm interested in how other engineers approach local caching in distributed systems.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How do you handle cache invalidation across service instances?&lt;/li&gt;
&lt;li&gt;Do you prefer event-driven invalidation, short TTLs, versioned keys, or a combination?&lt;/li&gt;
&lt;li&gt;What failure scenarios would you consider essential to address in this design?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're interested in the implementation, feedback and technical discussion are very welcome.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Less remote access, more ice.&lt;/em&gt; 🐻❄️🧊&lt;/p&gt;
&lt;h2 id=&quot;java-spring-microservices-caching-distributedsystems-opensource&quot;&gt;Java #Spring #Microservices #Caching #DistributedSystems #OpenSource&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32489/polar-bear-cache-local-caching-without-repeated-remote-calls</guid>
<pubDate>Mon, 26 Oct 2026 10:07:01 +0000</pubDate>
</item>
<item>
<title>Sync Session: Rethinking Session Management in Microservices</title>
<link>https://coderlegion.com/32486/sync-session-rethinking-session-management-in-microservices</link>
<description>&lt;h2 id=&quot;sync-session-rethinking-session-management-in-microservices&quot;&gt;Sync Session: Rethinking Session Management in Microservices&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/babyblue94520/sync-session&quot;&gt;Sync Session on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shared storage provides the source of truth. Local caches provide performance. Events synchronize session state across service instances.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since I started working with microservices in 2016, I have continuously explored and refined different approaches to session management.&lt;/p&gt;
&lt;p&gt;One question has remained particularly important to me:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How can every service independently verify a user's session without turning authentication into a bottleneck?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-challenge-security-vs-performance&quot;&gt;The Challenge: Security vs. Performance&lt;/h3&gt;
&lt;p&gt;In a microservices architecture, services frequently communicate with one another. From a zero-trust perspective, a service should not blindly trust a &lt;code&gt;userId&lt;/code&gt; simply because another service included it in a request.&lt;/p&gt;
&lt;p&gt;Instead, each service should validate the session and enforce the appropriate authorization rules.&lt;/p&gt;
&lt;p&gt;However, querying a shared database every time a service needs to validate a session can increase latency and database load, especially when requests pass through multiple services.&lt;/p&gt;
&lt;p&gt;This creates a practical engineering challenge: how do we preserve session validity and revocation capabilities while reducing the cost of repeated remote access?&lt;/p&gt;
&lt;p&gt;That question led me to develop &lt;strong&gt;Sync Session&lt;/strong&gt;, an open-source session management library for Spring Boot.&lt;/p&gt;
&lt;h3 id=&quot;the-design&quot;&gt;The Design&lt;/h3&gt;
&lt;p&gt;The design combines four main components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RDBMS as shared storage:&lt;/strong&gt; The persistent source of truth for session state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local cache:&lt;/strong&gt; Each service instance maintains a local copy of session data to reduce repeated database queries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event service:&lt;/strong&gt; Session changes trigger events that allow other service instances to invalidate their corresponding local cache entries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session ID propagation:&lt;/strong&gt; The session ID can be passed through a cookie or HTTP header, depending on the application's request flow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The session itself is intentionally kept small. Rather than embedding a large amount of user data and permissions into the session, the application uses a protected session ID to retrieve the associated authenticated identity.&lt;/p&gt;
&lt;p&gt;User information and authorization data remain under the responsibility of the relevant systems, where they can be updated independently.&lt;/p&gt;
&lt;p&gt;This separation helps avoid relying on outdated user information stored in a session and supports centralized session revocation.&lt;/p&gt;
&lt;h3 id=&quot;what-does-this-approach-support-&quot;&gt;What Does This Approach Support?&lt;/h3&gt;
&lt;p&gt;The project aims to address several practical requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Distributed session management across multiple service instances.&lt;/li&gt;
&lt;li&gt;Sliding session expiration, allowing the expiration time to be extended according to the configured policy.&lt;/li&gt;
&lt;li&gt;Administrative session revocation, enabling an administrator to force a user to log out.&lt;/li&gt;
&lt;li&gt;Synchronization of session invalidation across service instances.&lt;/li&gt;
&lt;li&gt;Reduced database access through local caching.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key idea is that services can validate sessions locally in the common case, while shared storage and event-driven invalidation help coordinate changes.&lt;/p&gt;
&lt;h3 id=&quot;performance-reference&quot;&gt;Performance Reference&lt;/h3&gt;
&lt;p&gt;In my MySQL load tests, I measured the following results under a workload involving 100 threads and 1 million sessions:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Operation &lt;/th&gt;&lt;th&gt;Measured throughput&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Session creation &lt;/td&gt;&lt;td&gt;~15,533 operations/sec&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Local cache access &lt;/td&gt;&lt;td&gt;~20.01 million operations/sec&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Direct database queries &lt;/td&gt;&lt;td&gt;~69,278 operations/sec&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All 1 million session creation operations completed successfully in this test, with no reported failures.&lt;/p&gt;
&lt;p&gt;Under these particular test conditions, local cache access achieved approximately &lt;strong&gt;289 times the throughput of direct database queries&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;These numbers are reference measurements from my test environment, not universal performance guarantees. The results depend on hardware, database configuration, query patterns, cache implementation, concurrency, and the exact benchmark methodology.&lt;/p&gt;
&lt;p&gt;The comparison also measures different access paths, so it should not be interpreted as an end-to-end authentication benchmark.&lt;/p&gt;
&lt;h3 id=&quot;consistency-and-security-trade-offs&quot;&gt;Consistency and Security Trade-offs&lt;/h3&gt;
&lt;p&gt;Local caching introduces an important limitation: event-driven invalidation is asynchronous.&lt;/p&gt;
&lt;p&gt;When a session is revoked, another service instance may still have a cached copy until it processes the invalidation event. Event delivery failures or temporary network partitions can extend this window.&lt;/p&gt;
&lt;p&gt;For security-sensitive systems, this behavior needs to be considered explicitly. Depending on the threat model, additional safeguards may be necessary, such as bounded cache lifetimes, reliable event delivery, recovery mechanisms, or synchronous validation for particularly sensitive operations.&lt;/p&gt;
&lt;p&gt;Likewise, validating a session is not the same as authorizing every operation. Services must still enforce their own authorization requirements, and internal service communication must be authenticated appropriately.&lt;/p&gt;
&lt;p&gt;The goal is not to claim that local caching removes every security or consistency challenge. It is to explore a practical balance between independent session validation, efficient reads, and distributed session invalidation.&lt;/p&gt;
&lt;h3 id=&quot;a-design-principle-worth-discussing&quot;&gt;A Design Principle Worth Discussing&lt;/h3&gt;
&lt;p&gt;The principle behind Sync Session can be summarized as:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shared storage for correctness. Local cache for performance. Events for synchronization.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is not the only valid architecture, and it may not be appropriate for every workload. I am sharing it because session management is a recurring challenge in distributed systems, and I believe the trade-offs deserve discussion beyond the implementation of any single library.&lt;/p&gt;
&lt;p&gt;I'd especially like to hear from engineers who have built authentication infrastructure at scale:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How do you handle session revocation across multiple service instances?&lt;/li&gt;
&lt;li&gt;Would you accept a short invalidation delay, or require immediate revocation for every request?&lt;/li&gt;
&lt;li&gt;How do you balance database load, cache consistency, and security requirements?&lt;/li&gt;
&lt;li&gt;What failure scenarios would you consider essential to address in this design?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm happy to discuss the implementation, benchmark methodology, and architectural trade-offs.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32486/sync-session-rethinking-session-management-in-microservices</guid>
<pubDate>Mon, 26 Oct 2026 09:17:47 +0000</pubDate>
</item>
<item>
<title>How to Protect a Microservices Architecture with a WAF: A Practical SafeLine Checklist</title>
<link>https://coderlegion.com/31920/how-to-protect-a-microservices-architecture-with-a-waf-a-practical-safeline-checklist</link>
<description>&lt;p&gt;Microservices improve deployment flexibility, but they also create a larger HTTP attack surface. Public APIs, authentication services, administration panels, checkout endpoints, and internal services exposed through an ingress layer may all receive traffic from the Internet. A web application firewall can provide a control point before requests reach those services, while service owners continue to manage application authentication, authorization, validation, and business logic.&lt;/p&gt;
&lt;p&gt;SafeLine works as an HTTP/HTTPS reverse proxy. It receives traffic for the original application, filters malicious requests, and forwards safe traffic to the upstream service. The official application guide describes this model and explains that an application can be added by configuring its domain, listening port, and upstream address: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;SafeLine Add Application Guide&lt;/a&gt;. For a microservices environment, the practical question is where to place that proxy boundary.&lt;/p&gt;
&lt;p&gt;A useful starting design is to place SafeLine at the public edge for Internet-facing domains. Route each protected hostname to the SafeLine server, then configure the corresponding upstream service or gateway. Keep the upstream services reachable from the proxy while limiting direct public access through the surrounding network controls. This makes the request path easy to reason about: client, SafeLine, ingress or service endpoint, and application.&lt;/p&gt;
&lt;p&gt;Use a separate application entry when services have different hostnames, ports, TLS requirements, or traffic policies. Begin with the services that handle sensitive data or accept unauthenticated traffic. Record the expected methods, paths, request sizes, and client patterns for each service before changing policy. This helps teams investigate a blocked request without treating every microservice as if it had identical traffic.&lt;/p&gt;
&lt;p&gt;SafeLine documentation lists defenses for attacks including SQL injection, XSS, code injection, OS command injection, CRLF injection, XXE, SSRF, path traversal, and related web attacks. It also documents rate limiting, CAPTCHA challenges, authentication challenges, dynamic protection for HTML and JavaScript, and access controls: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;SafeLine Home and Features&lt;/a&gt;. Apply these controls according to endpoint behavior. Rate limits can be relevant to login and password reset paths, while a CAPTCHA challenge may suit selected public forms. Avoid applying a challenge blindly to machine-to-machine APIs without testing the client workflow.&lt;/p&gt;
&lt;p&gt;For operations, define ownership for the proxy configuration, upstream health checks, incident review, and rollback. Keep a record of each hostname and its upstream target. Test expected requests as well as representative attack cases in a controlled environment. The application guide shows examples for SQL injection, XSS, path traversal, code injection, and XXE, and explains how to review attack details in the web console. Treat those examples as validation inputs for an authorized test environment, not as a substitute for application security testing.&lt;/p&gt;
&lt;p&gt;Deployment should match the supported environment. The installation guide lists Linux, x86_64 and arm64 instruction architectures, Docker 20.10.14 or above, Docker Compose 2.0.0 or above, and a minimum environment of one CPU core, 1 GB memory, and 5 GB disk: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;SafeLine Deploy Guide&lt;/a&gt;. Plan capacity around actual traffic and logging needs, and verify architecture specific requirements before deployment. The guide states that ARM installation requires a Pro license and that the Personal Edition is not supported on ARM.&lt;/p&gt;
&lt;p&gt;As of September 30, 2026, SafeLine surpassed 700,000 installations worldwide. SafeLine Pro annual promotional pricing is $650-$880 versus the $1,000 regular price. Confirm the applicable offer and deployment requirements through the official project materials before making a purchase or architecture decision.&lt;/p&gt;
&lt;h4 id=&quot;should-safeline-protect-every-microservice-&quot;&gt;Should SafeLine protect every microservice?&lt;/h4&gt;
&lt;p&gt;Start with Internet-facing services and shared entry points. A WAF is most useful where HTTP or HTTPS traffic crosses a trust boundary. Internal services may still need authentication, authorization, input validation, and network segmentation even when traffic passes through a protected gateway.&lt;/p&gt;
&lt;h4 id=&quot;how-should-upstream-routing-be-organized-&quot;&gt;How should upstream routing be organized?&lt;/h4&gt;
&lt;p&gt;Map each public domain to the intended upstream address and port, then verify DNS points to the SafeLine server. Keep a service inventory with owners, expected traffic, TLS settings, and rollback details. The official guide describes the domain, port, SSL option, and upstream fields used when adding an application.&lt;/p&gt;
&lt;h4 id=&quot;can-a-waf-replace-microservice-security-controls-&quot;&gt;Can a WAF replace microservice security controls?&lt;/h4&gt;
&lt;p&gt;No. SafeLine can filter and monitor HTTP traffic, but it does not replace identity controls, authorization checks, secure coding, secrets management, dependency updates, or service level logging. Use it as one enforcement layer in a broader architecture.&lt;/p&gt;
&lt;h4 id=&quot;what-should-be-tested-after-onboarding-a-service-&quot;&gt;What should be tested after onboarding a service?&lt;/h4&gt;
&lt;p&gt;Test normal user journeys, API clients, authentication flows, error handling, and rate-sensitive endpoints. Then run authorized cases for common web attacks and inspect the SafeLine attack list and details. Confirm that legitimate requests pass and that blocked requests produce enough information for the responsible team to investigate.&lt;/p&gt;
&lt;p&gt;Use this checklist to plan a measured rollout for your own microservices architecture, and review the official project and documentation for current deployment details. Project: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt; Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt; Promotion: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31920/how-to-protect-a-microservices-architecture-with-a-waf-a-practical-safeline-checklist</guid>
<pubDate>Wed, 21 Oct 2026 04:29:35 +0000</pubDate>
</item>
<item>
<title>How to Protect a Microservices Architecture with a WAF</title>
<link>https://coderlegion.com/31919/how-to-protect-a-microservices-architecture-with-a-waf</link>
<description>&lt;p&gt;Microservices expand delivery flexibility, but they also create more HTTP entry points, routing paths, and service boundaries to review. A practical WAF strategy begins by deciding where external traffic should enter the system and which applications need direct protection. SafeLine is documented as a self-hosted web application firewall that filters and monitors HTTP traffic between web applications and the Internet. It works as a reverse proxy, placing a protective layer in front of the application before requests reach the server (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;official overview&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Start by mapping the public surface of the architecture. List customer portals, authentication endpoints, API gateways, upload services, administrative interfaces, and any service exposed through a public domain. For each hostname, identify its upstream application, listening port, transport security requirements, expected request methods, and business criticality. This inventory helps you place SafeLine at the edge of each public application while keeping internal service-to-service traffic governed by the controls appropriate to your environment.&lt;/p&gt;
&lt;p&gt;SafeLine's application model follows this reverse-proxy pattern. In the Web Admin Console, an administrator adds an application, supplies its domain, listening port, optional SSL setting, and upstream address, then resolves the domain to the SafeLine server. SafeLine receives the request, filters malicious traffic, and forwards trusted traffic to the original application (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;application guide&lt;/a&gt;). For a microservices platform, use a clear hostname and upstream mapping for every externally reachable service. This makes logs and policy decisions easier to associate with a particular API or user-facing component.&lt;/p&gt;
&lt;p&gt;Use the WAF as one layer in a broader request policy. SafeLine's documented defenses include protection against SQL injection, XSS, code injection, OS command injection, CRLF injection, XXE, SSRF, path traversal, and related web attacks. Its documented capabilities also include IP-based rate limiting, CAPTCHA challenges, authentication challenges, proactive bot abuse defense, and web access control lists (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;official overview&lt;/a&gt;). Apply these controls according to endpoint behavior. Rate limits may be especially useful for login, password reset, search, and resource-intensive API routes. Authentication challenges can provide an additional barrier for selected administrative surfaces.&lt;/p&gt;
&lt;p&gt;Validate the arrangement with safe test cases in a controlled environment. The application guide provides examples for SQL injection, XSS, path traversal, code injection, and XXE testing, and explains that attack details can be reviewed in the SafeLine console (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/AddApplication&quot;&gt;application guide&lt;/a&gt;). Review blocked requests alongside legitimate traffic so that policies reflect real application behavior. Also confirm that upstream services are reachable through the intended path and that DNS, ports, and SSL settings match the deployment design.&lt;/p&gt;
&lt;p&gt;For self-hosted deployment, the getting started guide lists Linux, x86_64 or arm64, Docker 20.10.14 or later, Docker Compose 2.0.0 or later, and minimum requirements of one CPU core, 1 GB memory, and 5 GB disk (&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/GetStarted/Deploy&quot;&gt;deployment guide&lt;/a&gt;). Treat those as starting requirements and size the host according to traffic, logging, and operational needs. The same guide documents the Web Console, application data directory, and administrator account initialization process.&lt;/p&gt;
&lt;p&gt;As of September 30, 2026, SafeLine surpassed 700,000 installations worldwide. SafeLine Pro annual promotional pricing is $650-$880 versus the $1,000 regular price. These publication details should be verified against the current commercial materials before release.&lt;/p&gt;
&lt;h4 id=&quot;where-should-safeline-sit-in-a-microservices-deployment-&quot;&gt;Where should SafeLine sit in a microservices deployment?&lt;/h4&gt;
&lt;p&gt;Place SafeLine in front of the public web applications or API entry points that receive traffic from the Internet. Configure each protected application with its domain, listening port, and upstream address, then direct the domain to the SafeLine server as described in the official application guide.&lt;/p&gt;
&lt;h4 id=&quot;should-every-internal-service-be-exposed-through-the-waf-&quot;&gt;Should every internal service be exposed through the WAF?&lt;/h4&gt;
&lt;p&gt;Begin with externally reachable services and administrative surfaces. Internal services should remain subject to the access controls and network design used for internal traffic. Expose a service through SafeLine when it has a public HTTP or HTTPS entry point that requires web attack filtering or request abuse controls.&lt;/p&gt;
&lt;h4 id=&quot;how-can-teams-reduce-disruption-during-rollout-&quot;&gt;How can teams reduce disruption during rollout?&lt;/h4&gt;
&lt;p&gt;Inventory routes first, test representative legitimate requests, and use controlled attack examples from the application guide. Check the SafeLine attack list and application behavior after each change. This gives the team evidence about blocked attacks, expected traffic, and any policy adjustment needed before expanding coverage.&lt;/p&gt;
&lt;h4 id=&quot;what-should-be-reviewed-after-deployment-&quot;&gt;What should be reviewed after deployment?&lt;/h4&gt;
&lt;p&gt;Review application mappings, upstream reachability, SSL settings, rate-limit targets, authentication challenge scope, and attack records. Repeat the review when a service gains a new public endpoint or changes its request format.&lt;/p&gt;
&lt;p&gt;If you are planning a microservices security review, use the official materials to build your application inventory and deployment checklist, then explore SafeLine through the project page and documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/chaitin/SafeLine&quot;&gt;https://github.com/chaitin/SafeLine&lt;/a&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.waf.chaitin.com/en/home&quot;&gt;https://docs.waf.chaitin.com/en/home&lt;/a&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyberserval.tech/landing/lto&quot;&gt;https://cyberserval.tech/landing/lto&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31919/how-to-protect-a-microservices-architecture-with-a-waf</guid>
<pubDate>Wed, 21 Oct 2026 04:29:03 +0000</pubDate>
</item>
<item>
<title>The Order Was Saved. The Kafka Event Might Never Arrive.</title>
<link>https://coderlegion.com/30973/the-order-was-saved-the-kafka-event-might-never-arrive</link>
<description>&lt;p&gt;A customer clicks &lt;strong&gt;Place order&lt;/strong&gt;. Your API returns success. The order row is in the database. But the service that processes orders never sees an event.&lt;/p&gt;
&lt;p&gt;This is not a hypothetical edge case invented by Kafka. It is a failure window in a common Spring Boot pattern: save to a database, then publish to a broker as a second operation. I found the pattern while reviewing my own event-driven order processing capstone. &lt;strong&gt;This is a code review of a learning project, not a claim that a production outage occurred.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The lesson applies well beyond orders. If your API writes data and then emits an event for fraud checks, notifications, inventory, or analytics, a successful HTTP response can tell less of the story than you think.&lt;/p&gt;
&lt;h3 id=&quot;the-four-lines-that-made-me-stop&quot;&gt;The four lines that made me stop&lt;/h3&gt;
&lt;p&gt;My order service saves a new order, then calls a producer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CustomerOrder order = orderRepository.save(new CustomerOrder(...));
producer.publish(new OrderEvent(order.getId(), ...));
return toResponse(order);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The producer uses Spring Kafka like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void publish(OrderEvent event) {
    kafkaTemplate.send(&quot;order-events&quot;, String.valueOf(event.orderId()), event);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That send returns a future. In this implementation, the future is ignored. The method can move on to the HTTP response without observing whether Kafka acknowledged the record. More fundamentally, the database write and Kafka publish are two separate operations. A process crash can occur between them even if you wait for the send result.&lt;/p&gt;
&lt;p&gt;The code is perfectly useful for demonstrating an event-driven flow. It is not enough by itself to promise that every saved order will produce a durable event.&lt;/p&gt;
&lt;h3 id=&quot;where-the-gap-opens&quot;&gt;Where the gap opens&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=14888194715637270969&quot; alt=&quot;Diagram: database commit, asynchronous Kafka send, and the failure window before event confirmation&quot;&gt;&lt;/p&gt;
&lt;p&gt;There are at least two ways to lose the intended handoff:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Crash after the database commit, before the send.&lt;/strong&gt; The order exists, but no event was requested.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Asynchronous send failure after the method proceeds.&lt;/strong&gt; The producer future completes with an error, but this code does not inspect it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The reverse uncertainty also matters: Kafka may accept an event while the client never receives the HTTP response. A retry from the client can create another order unless the API has a separate request-idempotency rule.&lt;/p&gt;
&lt;p&gt;I already had retries and a dead-letter topic on the &lt;strong&gt;consumer&lt;/strong&gt; side of the capstone. Those help when an event reaches Kafka and processing fails. They cannot recover an event that was never published. This distinction is easy to miss when a demo looks healthy end to end.&lt;/p&gt;
&lt;h3 id=&quot;first-choose-what-a-successful-response-means&quot;&gt;First, choose what a successful response means&lt;/h3&gt;
&lt;p&gt;An API contract should say what has actually happened. For an asynchronous workflow, a successful create response may mean &lt;strong&gt;the order and the intent to process it have been stored&lt;/strong&gt;. It should not imply that downstream processing is complete.&lt;/p&gt;
&lt;p&gt;A practical state model might be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATED → PROCESSING → APPROVED or REJECTED
                       ↘ NEEDS_ATTENTION
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A GET endpoint lets the caller check the current state. An order stuck in CREATED for too long should be visible to monitoring and operators, not quietly forgotten.&lt;/p&gt;
&lt;h3 id=&quot;the-transactional-outbox-fix&quot;&gt;The transactional outbox fix&lt;/h3&gt;
&lt;p&gt;A transactional outbox makes the event intent part of the same database transaction as the order. The API writes two rows: the business record and an outbox record containing an event ID, aggregate ID, event type, and payload. Either both rows commit or neither does.&lt;/p&gt;
&lt;p&gt;A separate relay publishes committed outbox rows to Kafka. That relay can poll the table or use change-data capture. Debezium's outbox event router is one established implementation of the latter approach.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=7037414946828488191&quot; alt=&quot;Diagram: order and outbox rows committed together, then relayed to Kafka with downstream deduplication&quot;&gt;&lt;/p&gt;
&lt;p&gt;The core application code becomes conceptually:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Transactional
public OrderResponse createOrder(CreateOrderRequest request) {
    CustomerOrder order = orderRepository.save(toEntity(request));
    outboxRepository.save(OutboxEvent.orderCreated(order));
    return toResponse(order);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is &lt;strong&gt;illustrative replacement code&lt;/strong&gt;, not a feature already implemented in my capstone. The important part is the transaction boundary around both inserts. The relay is responsible for eventual delivery after commit.&lt;/p&gt;
&lt;p&gt;The outbox does not turn the whole system into exactly-once delivery. A relay may publish an event and crash before recording that it published it. It will publish again after restart. Consumers therefore need to handle duplicates, usually by recording a stable event ID and making the business effect idempotent.&lt;/p&gt;
&lt;h3 id=&quot;two-ids-solve-two-different-retry-problems&quot;&gt;Two IDs solve two different retry problems&lt;/h3&gt;
&lt;p&gt;It helps to keep these IDs distinct:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Request ID:&lt;/strong&gt; supplied or assigned at the API edge so a repeated Place order request does not create a second order.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event ID:&lt;/strong&gt; stored with the outbox record so repeated event delivery does not apply the same downstream action twice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kafka producer idempotence is useful for producer retries, but it does not make a database transaction and a broker publish atomic. Nor does it replace consumer-side deduplication across relay restarts. Each layer protects a different boundary.&lt;/p&gt;
&lt;h3 id=&quot;the-test-i-would-add-before-calling-this-production-ready&quot;&gt;The test I would add before calling this production-ready&lt;/h3&gt;
&lt;p&gt;I would make failure injection part of the acceptance test:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an order while the broker is unavailable.&lt;/li&gt;
&lt;li&gt;Confirm that the order and its outbox event are committed together.&lt;/li&gt;
&lt;li&gt;Restore the broker and confirm that the relay publishes the event.&lt;/li&gt;
&lt;li&gt;Force the relay to retry, then confirm the consumer applies the event only once.&lt;/li&gt;
&lt;li&gt;Check that the order eventually leaves CREATED, or raises an alert if it does not.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the current direct-send design, a broker failure or crash can leave an order without a durable event intent. The test should make that gap visible. In the outbox design, the same test checks the recovery path rather than just the happy path.&lt;/p&gt;
&lt;h3 id=&quot;what-i-would-monitor&quot;&gt;What I would monitor&lt;/h3&gt;
&lt;p&gt;A reliable event-driven API needs signals beyond HTTP success rate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Age of the oldest unpublished outbox row&lt;/li&gt;
&lt;li&gt;Number of publish failures and relay retries&lt;/li&gt;
&lt;li&gt;Consumer lag and dead-letter count&lt;/li&gt;
&lt;li&gt;Duplicate event count&lt;/li&gt;
&lt;li&gt;Orders stuck in CREATED or PROCESSING&lt;/li&gt;
&lt;li&gt;Time from order creation to final state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These metrics turn “the dashboard never updated” into an observable state with an owner and a recovery path.&lt;/p&gt;
&lt;h3 id=&quot;the-takeaway&quot;&gt;The takeaway&lt;/h3&gt;
&lt;p&gt;My capstone taught me a useful production lesson: &lt;strong&gt;an event-driven workflow is only as reliable as the boundary between its database and its broker.&lt;/strong&gt; Consumer retries are valuable, but they start too late to repair a missing publish. Save the business record and the event intent together, deliver at least once, deduplicate downstream, and monitor the gap between creation and completion.&lt;/p&gt;
&lt;p&gt;If you have built a database-to-Kafka flow, where do you persist the intent to publish? I would be interested to hear how your team tests that failure window.&lt;/p&gt;
&lt;h4 id=&quot;references&quot;&gt;References&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.spring.io/spring-kafka/reference/kafka/sending-messages.html&quot;&gt;Spring Kafka: Sending Messages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://debezium.io/documentation/reference/stable/transformations/outbox-event-router.html&quot;&gt;Debezium: Outbox Event Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kafka.apache.org/documentation/#semantics&quot;&gt;Apache Kafka: Design and Delivery Semantics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30973/the-order-was-saved-the-kafka-event-might-never-arrive</guid>
<pubDate>Tue, 13 Oct 2026 17:20:19 +0000</pubDate>
</item>
<item>
<title>Stop Treating EDA as Just Pub/Sub. Here’s How to Handle Request/Reply</title>
<link>https://coderlegion.com/30621/stop-treating-eda-as-just-pub-sub-heres-how-to-handle-request-reply</link>
<description>&lt;h2 id=&quot;-eda-just-pub-sub-building-flexible-request-reply-journeys-through-event-choreography&quot;&gt;⚡ EDA ≠ Just Pub/Sub: Building Flexible Request/Reply Journeys Through Event Choreography&lt;/h2&gt;
&lt;blockquote&gt;&lt;p&gt;🚀 &lt;strong&gt;Exciting Update:&lt;/strong&gt; Incredibly honored that this deep dive was selected for the &lt;strong&gt;HackerNoon Science Tech Brief&lt;/strong&gt; within 12 hours of its original release! Sharing it here with the Coder Legion community to hear your thoughts on EDA patterns.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Note: The complete, ready-to-use article template covering the core dilemma of synchronous request/reply flows, the multi-payment processing blueprint with topic naming standards, zero-routing choreography principles, and engagement prompts for the community is fully prepared for your publication.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⚡ The Hidden Power of EDA: Building Flexible Request/Reply Journeys Through Event Choreography&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When engineers discuss Event-Driven Architecture (EDA), the conversation typically centers on asynchronous pub/sub patterns, fire-and-forget processing, and real-time streaming. We praise EDA for decoupling our systems, but we often hit a wall when a core business process inherently demands a synchronous request-reply interaction.&lt;/p&gt;
&lt;p&gt;A common misconception is that EDA is poorly suited for request-reply workflows. The standard instinct is to fall back on direct, synchronous HTTP/gRPC service calls between microservices. However, this introduces tight coupling, making it difficult to scale, evolve, or independently deploy components.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;🎯 The Challenge&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In traditional request/reply designs, systems can become tightly coupled through direct service calls, making it harder to scale, evolve, or introduce new capabilities into the workflow.&lt;/p&gt;
&lt;p&gt;A well-designed event-driven choreography offers an alternative approach.&lt;/p&gt;
&lt;p&gt;It enables an end-to-end business process to flow seamlessly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Client Request&lt;/li&gt;
&lt;li&gt;Backend Services&lt;/li&gt;
&lt;li&gt;Multiple Processing Components&lt;/li&gt;
&lt;li&gt;Business Decisions&lt;/li&gt;
&lt;li&gt;Final Response to Client&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regardless of how many systems participate in the journey.&lt;/p&gt;
&lt;p&gt;As a result, EDA becomes more than a mechanism for event distribution. It becomes a powerful foundation for building scalable and flexible request/reply workflows.&lt;/p&gt;
&lt;p&gt;💳 Example: Multi-Payment Processing Platform&lt;/p&gt;
&lt;p&gt;Let's use a payment platform that supports multiple payment methods as an example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bank Transfer&lt;/li&gt;
&lt;li&gt;FPS&lt;/li&gt;
&lt;li&gt;PPS&lt;/li&gt;
&lt;li&gt;EFT&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Although these payment methods follow different processing paths, they can share the same event-driven framework.&lt;br&gt;
&lt;strong&gt;📌 Step 1: Establish a Well-Defined Topic Naming Standard&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The well-defined topic structure with mandatory fields and optional fields. The mandatory part should include at least four fields: information domain, information subdomain, message type, and schema version.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=15090109762337142705&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;📌 Step 2: Use the Naming Standard as the Event Flow Guideline&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Events may travel through many systems during their lifecycle.&lt;/li&gt;
&lt;li&gt;Although different systems may interpret or process the event&lt;br&gt;
differently, the original business context must remain intact. &lt;/li&gt;
&lt;li&gt;The topic representing the event should remain consistent with the&lt;br&gt;
event's original business purpose.&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=72657400590844178&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=1121929266061598601&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;On each event flow, the mandatory parts (red and blue colors) are placed in the message header and brought forward to all subsequent messages in the flow. This part should not be changed throughout the lifetime of the event flow. For each component, the output topic will be the input messages’ mandatory part plus this component function, plus the process status. It is very easy to subscribe to all corresponding events within the same event flow by using a wildcard.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;🧩 Loosely Coupled Processing Components&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An important characteristic of this design is that individual processing components do not contain built-in routing logic.&lt;/p&gt;
&lt;p&gt;Each component simply:&lt;/p&gt;
&lt;p&gt;📥 Consumes events it is interested in &lt;br&gt;
⚙️ Performs its business function &lt;br&gt;
📤 Publishes the next event&lt;/p&gt;
&lt;p&gt;The component has no knowledge of:&lt;/p&gt;
&lt;p&gt;❌ Which flow it belongs to &lt;br&gt;
❌ Which system produced the event &lt;br&gt;
❌ Which system will consume the next event&lt;/p&gt;
&lt;p&gt;This significantly improves reusability and maintainability.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=5615199361192018044&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;✅ The Result&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This architecture enables a set of common reusable components to support multiple request/reply workflows across different channels and payment methods.&lt;/p&gt;
&lt;p&gt;Key Outcomes&lt;/p&gt;
&lt;p&gt;Consistent event schema across multiple use cases&lt;br&gt;
All downstream events inherit and extend the original business context&lt;br&gt;
Additional analytical services can be introduced without impacting existing applications&lt;br&gt;
Customer behavior and application activities can be monitored in real time&lt;br&gt;
New processing components can be added or removed easily&lt;br&gt;
Existing applications require no code changes&lt;br&gt;
Most changes can be achieved through topic or queue subscription configuration alone&lt;/p&gt;
&lt;p&gt;💡 Final Thought&lt;/p&gt;
&lt;p&gt;One of the biggest misconceptions about Event-Driven Architecture is that it is only suitable for asynchronous event broadcasting.&lt;/p&gt;
&lt;p&gt;In reality, when combined with strong event governance, consistent topic standards, and event choreography, EDA can also provide an elegant solution for complex request/reply interactions.&lt;/p&gt;
&lt;p&gt;The result is an architecture that is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Loosely Coupled &lt;/li&gt;
&lt;li&gt;Highly Scalable &lt;/li&gt;
&lt;li&gt;Easily Extensible &lt;/li&gt;
&lt;li&gt;Business-Driven &lt;/li&gt;
&lt;li&gt;Future-Proof&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How does your organization handle request/reply patterns in an event-driven landscape? I'd love to hear your experiences and perspectives in the comments.&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30621/stop-treating-eda-as-just-pub-sub-heres-how-to-handle-request-reply</guid>
<pubDate>Tue, 13 Oct 2026 05:13:00 +0000</pubDate>
</item>
<item>
<title>Stop Wasting Days on Dev Setup: How We Automated Our Microservices Environment for Every Developer</title>
<link>https://coderlegion.com/28399/stop-wasting-days-dev-setup-how-automated-our-microservices-environment-every-developer</link>
<description>&lt;h2 id=&quot;stop-wasting-days-on-dev-setup-how-we-automated-our-microservices-environment-for-every-developer&quot;&gt;Stop Wasting Days on Dev Setup: How We Automated Our Microservices Environment for Every Developer&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;How we reduced new developer onboarding from 3 days to 15 minutes and eliminated configuration drift across 12 microservices.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-that-made-us-snap&quot;&gt;The Problem That Made Us Snap&lt;/h3&gt;
&lt;p&gt;Picture this: A new senior developer joins your team. They're excited, ready to ship code on day one. Then reality hits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 1:&lt;/strong&gt; They spend 6 hours installing Node, Docker, configuring ESLint to match the team's style, figuring out which environment variables go where, and asking Slack questions like &lt;em&gt;&quot;which version of NestJS are we on again?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 2:&lt;/strong&gt; They finally get &lt;code&gt;user-service&lt;/code&gt; running, but &lt;code&gt;auth-service&lt;/code&gt; throws a CORS error because their local config is slightly different from everyone else's. They spend the afternoon comparing &lt;code&gt;.env&lt;/code&gt; files with a teammate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 3:&lt;/strong&gt; They submit their first PR. The CI pipeline fails because their local Prettier config formats code differently from the shared config that was updated last month — but nobody told them.&lt;/p&gt;
&lt;p&gt;Sound familiar?&lt;/p&gt;
&lt;p&gt;We had &lt;strong&gt;12 microservices&lt;/strong&gt;, a team that was growing fast, and a configuration mess that was eating our productivity alive. Every service had slightly different ESLint rules, Docker setups, folder structures, and VS Code settings. Senior developers were spending hours every week answering setup questions instead of building features.&lt;/p&gt;
&lt;p&gt;So we built a system that fixed all of it. Here's exactly how.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-solution-one-repo-to-rule-them-all&quot;&gt;The Solution: One Repo to Rule Them All&lt;/h3&gt;
&lt;p&gt;We created a &lt;strong&gt;central configuration repository&lt;/strong&gt; called &lt;code&gt;dev-environment-config&lt;/code&gt; that acts as the single source of truth for every development environment across our entire microservices ecosystem.&lt;/p&gt;
&lt;p&gt;The idea is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;One repository controls the setup, structure, linting, Docker config, VS Code settings, and shared code for every service. When a senior developer updates it, every developer's machine automatically syncs the next morning.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;the-architecture-at-a-glance&quot;&gt;The Architecture at a Glance&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;dev-environment-config (Central Repo)
├── templates/          → Project scaffolding (NestJS, React)
├── shared/             → ESLint, Prettier, Git configs
├── vscode/             → Extensions &amp;amp; settings
├── docker/             → Base Docker configs
├── scripts/            → Automation (setup, sync, update)
├── common-lib/         → Shared NPM package
└── manifest.json       → Version tracking

         │  syncs to
         ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ user-svc │ │ auth-svc │ │ pay-svc  │  ... 12 services
│ .eslint  │ │ .eslint  │ │ .eslint  │
│ .vscode  │ │ .vscode  │ │ .vscode  │
│ Docker   │ │ Docker   │ │ Docker   │
└──────────┘ └──────────┘ └──────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;implementation-step-by-step&quot;&gt;Implementation: Step by Step&lt;/h3&gt;
&lt;h4 id=&quot;step-1-the-central-config-repository&quot;&gt;Step 1: The Central Config Repository&lt;/h4&gt;
&lt;p&gt;We created a new repository with this structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dev-environment-config/
├── templates/
│   ├── nestjs-service/    # Complete NestJS starter
│   └── reactjs-service/   # Complete React starter
├── shared/
│   ├── .eslintrc.js
│   ├── .prettierrc
│   ├── .editorconfig
│   ├── .gitignore
│   └── commitlint.config.js
├── vscode/
│   ├── extensions.json
│   ├── settings.json
│   └── launch.json
├── scripts/
│   ├── setup-new-developer.sh
│   ├── sync-config.sh
│   ├── auto-update.sh
│   └── create-new-service.sh
├── common-lib/            # Shared NPM package
└── manifest.json          # Version tracker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;manifest.json&lt;/code&gt; is the heartbeat of the system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;version&quot;: &quot;1.2.0&quot;,
  &quot;lastUpdated&quot;: &quot;2024-01-15T10:00:00Z&quot;,
  &quot;updatedBy&quot;: &quot;senior-dev&quot;,
  &quot;configs&quot;: {
    &quot;eslint&quot;: &quot;3.2.0&quot;,
    &quot;prettier&quot;: &quot;2.1.0&quot;,
    &quot;docker&quot;: &quot;1.5.0&quot;
  },
  &quot;services&quot;: [
    &quot;user-service&quot;,
    &quot;auth-service&quot;,
    &quot;payment-service&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every time a config changes, we bump the version. This is how the auto-update system knows something changed.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-2-the-one-command-onboarding-script&quot;&gt;Step 2: The One-Command Onboarding Script&lt;/h4&gt;
&lt;p&gt;This was the game-changer. We wrote a bash script that a new developer runs &lt;strong&gt;once&lt;/strong&gt;, and their entire environment is ready.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The new developer's experience:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install VS Code ✅&lt;/li&gt;
&lt;li&gt;Run one command:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -sSL https://raw.githubusercontent.com/company/\
dev-environment-config/main/scripts/\
setup-new-developer.sh | bash
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Wait ~10 minutes. Done.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;What the script does under the hood:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
set -e

WORKSPACE_DIR=&quot;$HOME/company-workspace&quot;
CONFIG_REPO=&quot;https://github.com/company/dev-environment-config.git&quot;

echo &quot;🚀 Setting up your development environment...&quot;

# 1. Check &amp;amp; install prerequisites
check_and_install &quot;git&quot; &quot;Git&quot;
check_and_install &quot;node&quot; &quot;Node.js&quot; &quot;nvm install 20&quot;
check_and_install &quot;docker&quot; &quot;Docker&quot;

# 2. Create workspace &amp;amp; clone everything
mkdir -p &quot;$WORKSPACE_DIR&quot; &amp;amp;&amp;amp; cd &quot;$WORKSPACE_DIR&quot;
git clone &quot;$CONFIG_REPO&quot;

# 3. Clone all service repos from manifest
SERVICES=$(cat dev-environment-config/manifest.json | \
  python3 -c &quot;import sys,json; \
  [print(s) for s in json.load(sys.stdin)['services']]&quot;)

for service in $SERVICES; do
  git clone &quot;https://github.com/company/${service}.git&quot;
done

# 4. Install VS Code extensions
EXTENSIONS=$(cat dev-environment-config/vscode/extensions.json | \
  python3 -c &quot;import sys,json; \
  [print(e) for e in json.load(sys.stdin)['recommendations']]&quot;)

for ext in $EXTENSIONS; do
  code --install-extension &quot;$ext&quot; --force
done

# 5. Sync shared configs to all services
bash dev-environment-config/scripts/sync-config.sh

# 6. Install dependencies everywhere
for service in $SERVICES; do
  cd &quot;$service&quot; &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; cp .env.example .env &amp;amp;&amp;amp; cd ..
done

# 7. Set up daily auto-update cron (8 AM weekdays)
CRON=&quot;0 8 * * 1-5 cd $WORKSPACE_DIR &amp;amp;&amp;amp; \
  bash dev-environment-config/scripts/auto-update.sh&quot;
(crontab -l 2&amp;gt;/dev/null; echo &quot;$CRON&quot;) | crontab -

echo &quot;✅ Setup complete! Open VS Code: code $WORKSPACE_DIR&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. The developer opens VS Code, and everything is configured — extensions, linting, formatting, Docker, environment variables, the works.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-3-the-config-sync-script&quot;&gt;Step 3: The Config Sync Script&lt;/h4&gt;
&lt;p&gt;This script copies the shared configs from the central repo into every service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# sync-config.sh

CONFIG_ROOT=&quot;$(dirname &quot;$SCRIPT_DIR&quot;)&quot;
WORKSPACE_DIR=&quot;$(dirname &quot;$CONFIG_ROOT&quot;)&quot;

for service in $SERVICES; do
  SERVICE_DIR=&quot;$WORKSPACE_DIR/$service&quot;

  # Copy shared configs
  cp -f &quot;$CONFIG_ROOT/shared/.eslintrc.js&quot; &quot;$SERVICE_DIR/&quot;
  cp -f &quot;$CONFIG_ROOT/shared/.prettierrc&quot; &quot;$SERVICE_DIR/&quot;
  cp -f &quot;$CONFIG_ROOT/shared/.editorconfig&quot; &quot;$SERVICE_DIR/&quot;
  cp -f &quot;$CONFIG_ROOT/shared/.gitignore&quot; &quot;$SERVICE_DIR/&quot;

  # Copy VS Code settings
  mkdir -p &quot;$SERVICE_DIR/.vscode&quot;
  cp -f &quot;$CONFIG_ROOT/vscode/settings.json&quot; &quot;$SERVICE_DIR/.vscode/&quot;
  cp -f &quot;$CONFIG_ROOT/vscode/extensions.json&quot; &quot;$SERVICE_DIR/.vscode/&quot;

  echo &quot;✅ $service synced&quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-4-the-daily-auto-update-the-magic-&quot;&gt;Step 4: The Daily Auto-Update (The Magic)&lt;/h4&gt;
&lt;p&gt;This is the part that keeps everything in sync without anyone thinking about it.&lt;/p&gt;
&lt;p&gt;Every morning at 8 AM (weekdays), a cron job runs &lt;code&gt;auto-update.sh&lt;/code&gt; on every developer's machine:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# auto-update.sh — Runs daily at 8 AM

cd &quot;$CONFIG_ROOT&quot;

# 1. Check for config updates
OLD_VERSION=$(cat manifest.json | jq -r '.version')
git pull origin main
NEW_VERSION=$(cat manifest.json | jq -r '.version')

if [ &quot;$OLD_VERSION&quot; != &quot;$NEW_VERSION&quot; ]; then
  echo &quot;🆕 Config updated: $OLD_VERSION → $NEW_VERSION&quot;
  bash &quot;$SCRIPT_DIR/sync-config.sh&quot;
fi

# 2. Pull latest code for all services
for service in $SERVICES; do
  cd &quot;$WORKSPACE_DIR/$service&quot;

  # Stash local changes safely
  git stash push -m &quot;auto-update-$(date +%Y%m%d)&quot; 2&amp;gt;/dev/null

  # Pull latest
  git pull origin $(git branch --show-current) --rebase

  # Update dependencies if package.json changed
  if git diff HEAD~1 --name-only | grep -q &quot;package.json&quot;; then
    npm install
  fi

  # Update shared library
  npm update @company/common-lib
done

# 3. Notify the developer
notify-send &quot;Dev Environment&quot; &quot;✅ Updated to config v$NEW_VERSION&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The flow:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Senior Dev pushes config change
         │
         ▼
   GitHub Actions
         │
    ┌────┴────┐
    │ Slack   │ Notify team
    │ Publish │ common-lib to NPM
    └────┬────┘
         │
    Next 8 AM
         │
         ▼
  Every developer's cron runs
         │
    ┌────┴────────────────┐
    │ Pull config repo    │
    │ Sync shared configs │
    │ Pull service repos  │
    │ Update dependencies │
    │ Desktop notification│
    └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-5-the-shared-common-library&quot;&gt;Step 5: The Shared Common Library&lt;/h4&gt;
&lt;p&gt;Instead of copying and pasting guards, interceptors, and DTOs across 12 services, we created &lt;code&gt;@company/common-lib&lt;/code&gt; — a private NPM package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// In any service, just import and use:
import {
  AuthGuard,
  RolesGuard,
  ResponseInterceptor,
  LoggingInterceptor,
  HttpExceptionFilter,
  PaginationDto,
  McpService,        // Qwen MCP integration
} from '@company/common-lib';

@Controller('users')
@UseGuards(AuthGuard, RolesGuard)
@UseInterceptors(ResponseInterceptor)
@UseFilters(HttpExceptionFilter)
export class UserController {
  constructor(private mcpService: McpService) {}

  @Get()
  findAll(@Query() pagination: PaginationDto) {
    return this.userService.findAll(pagination);
  }

  @Post('analyze')
  async analyze(@Body() dto: AnalyzeDto) {
    return this.mcpService.callTool('text-analysis', {
      text: dto.text,
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we update the common library (add a new guard, fix a bug in the exception filter), the auto-update system bumps the version in every service automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-6-service-scaffolding&quot;&gt;Step 6: Service Scaffolding&lt;/h4&gt;
&lt;p&gt;Creating a new microservice used to take a day of copy-pasting and tweaking. Now it takes 30 seconds:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash scripts/create-new-service.sh

# Prompts:
# Service name: order-service
# Description: Handles order processing
# Port: 3002
# Type: nestjs

# ✅ Done! Complete service with:
# - src/common/ (filters, guards, interceptors, DTOs)
# - Docker setup (Dockerfile + docker-compose)
# - VS Code settings
# - ESLint + Prettier
# - Health check endpoint
# - Swagger docs
# - MCP integration
# - Auto-update scripts
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-benefits-with-real-numbers-&quot;&gt;The Benefits (With Real Numbers)&lt;/h3&gt;
&lt;h4 id=&quot;1-onboarding-3-days-15-minutes&quot;&gt;1. Onboarding: 3 Days → 15 Minutes&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Before &lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Manual setup following a 40-step Confluence page &lt;/td&gt;&lt;td&gt;One &lt;code&gt;curl&lt;/code&gt; command&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3 days to first productive commit &lt;/td&gt;&lt;td&gt;15 minutes to running all services&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Hey, can you help me with my env vars?&quot; (daily Slack noise) &lt;/td&gt;&lt;td&gt;Zero setup questions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;2-configuration-drift-eliminated&quot;&gt;2. Configuration Drift: Eliminated&lt;/h4&gt;
&lt;p&gt;Before, every service slowly diverged. &lt;code&gt;user-service&lt;/code&gt; had ESLint v8, &lt;code&gt;auth-service&lt;/code&gt; had v7. Prettier configs had subtle differences. Docker setups were copy-pasted and never updated.&lt;/p&gt;
&lt;p&gt;Now, &lt;strong&gt;every service has identical configs&lt;/strong&gt;, and they stay identical because the sync script overwrites them daily.&lt;/p&gt;
&lt;h4 id=&quot;3-senior-developer-time-saved-5-hours-week&quot;&gt;3. Senior Developer Time: Saved ~5 Hours/Week&lt;/h4&gt;
&lt;p&gt;Senior devs were spending roughly an hour a day helping juniors with setup issues, reviewing PRs that failed CI due to formatting, and manually updating configs across services.&lt;/p&gt;
&lt;p&gt;That time is now spent building features.&lt;/p&gt;
&lt;h4 id=&quot;4-consistent-code-quality&quot;&gt;4. Consistent Code Quality&lt;/h4&gt;
&lt;p&gt;Every developer has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The same ESLint rules (auto-fixed on save)&lt;/li&gt;
&lt;li&gt;The same Prettier config (format on save)&lt;/li&gt;
&lt;li&gt;The same commit message format (enforced by Husky)&lt;/li&gt;
&lt;li&gt;The same VS Code extensions (auto-installed)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PR reviews went from &lt;em&gt;&quot;please fix your formatting&quot;&lt;/em&gt; to &lt;em&gt;&quot;let's discuss the architecture.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;5-zero-effort-config-updates&quot;&gt;5. Zero-Effort Config Updates&lt;/h4&gt;
&lt;p&gt;When a senior developer wants to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a new ESLint rule → Push to config repo → Everyone gets it tomorrow&lt;/li&gt;
&lt;li&gt;Update Docker base image → Push → Auto-synced&lt;/li&gt;
&lt;li&gt;Add a VS Code extension → Push → Auto-installed&lt;/li&gt;
&lt;li&gt;Fix a bug in the exception filter → Publish common-lib → Auto-updated&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No Slack announcements needed. No &quot;please run &lt;code&gt;npm install&lt;/code&gt;&quot; messages. It just happens.&lt;/p&gt;
&lt;h4 id=&quot;6-standardized-project-structure&quot;&gt;6. Standardized Project Structure&lt;/h4&gt;
&lt;p&gt;Every service looks the same. A developer can jump from &lt;code&gt;user-service&lt;/code&gt; to &lt;code&gt;payment-service&lt;/code&gt; and immediately know where everything is. The &lt;code&gt;src/common/&lt;/code&gt; folder has the same patterns. The Docker setup is identical. The health check endpoint is at the same path.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-we-learned&quot;&gt;What We Learned&lt;/h3&gt;
&lt;h4 id=&quot;start-small&quot;&gt;Start Small&lt;/h4&gt;
&lt;p&gt;We didn't build this all at once. We started with just syncing ESLint and Prettier configs. Once the team saw the value, we added Docker, VS Code settings, the common library, and the auto-update system incrementally.&lt;/p&gt;
&lt;h4 id=&quot;don-t-force-everything&quot;&gt;Don't Force Everything&lt;/h4&gt;
&lt;p&gt;We allow service-specific overrides. If &lt;code&gt;payment-service&lt;/code&gt; needs a special ESLint rule, they can add a &lt;code&gt;.eslintrc.local.js&lt;/code&gt; that extends the shared one. The shared config is the baseline, not a straitjacket.&lt;/p&gt;
&lt;h4 id=&quot;notifications-matter&quot;&gt;Notifications Matter&lt;/h4&gt;
&lt;p&gt;The desktop notification (&quot;✅ Updated to config v1.2.0&quot;) was a small touch, but it made developers &lt;em&gt;aware&lt;/em&gt; of the system. Before that, updates happened silently and people were confused when things changed.&lt;/p&gt;
&lt;h4 id=&quot;version-everything&quot;&gt;Version Everything&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;manifest.json&lt;/code&gt; version tracking was critical. Without it, the auto-update script had no way to know if anything changed, and it would waste time syncing configs that were already up to date.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-complete-stack&quot;&gt;The Complete Stack&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Technology&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Backend &lt;/td&gt;&lt;td&gt;NestJS (TypeScript)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Frontend &lt;/td&gt;&lt;td&gt;ReactJS (TypeScript)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AI &lt;/td&gt;&lt;td&gt;Qwen MCP&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Containers &lt;/td&gt;&lt;td&gt;Docker, Docker Compose&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Runtime &lt;/td&gt;&lt;td&gt;Node.js 20 LTS&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Database &lt;/td&gt;&lt;td&gt;PostgreSQL 16&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cache &lt;/td&gt;&lt;td&gt;Redis 7&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CI/CD &lt;/td&gt;&lt;td&gt;GitHub Actions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Editor &lt;/td&gt;&lt;td&gt;VS Code&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Config Sync &lt;/td&gt;&lt;td&gt;Bash scripts + Cron&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;tl-dr&quot;&gt;TL;DR&lt;/h3&gt;
&lt;p&gt;If your team has more than 3 microservices and more than 5 developers, you need a centralized dev environment config. Here's the recipe:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create a config repo&lt;/strong&gt; with templates, shared configs, and scripts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write a setup script&lt;/strong&gt; that onboards new devs in one command&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write a sync script&lt;/strong&gt; that copies shared configs to all services&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set up a cron job&lt;/strong&gt; that auto-updates every morning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a shared NPM library&lt;/strong&gt; for reusable code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build a service generator&lt;/strong&gt; for scaffolding new microservices&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result: developers spend their time writing code, not configuring environments.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Have you solved this problem differently at your company? I'd love to hear about it in the comments.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you found this useful, give it a clap 👏 and follow for more microservices architecture deep-dives.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/28399/stop-wasting-days-dev-setup-how-automated-our-microservices-environment-every-developer</guid>
<pubDate>Thu, 24 Sep 2026 01:52:08 +0000</pubDate>
</item>
<item>
<title>A Tiny Nim Microservice, Three Real Bugs, and What They Taught Me About My Own Mummy Fork</title>
<link>https://coderlegion.com/27202/a-tiny-nim-microservice-three-real-bugs-and-what-they-taught-me-about-my-own-mummy-fork</link>
<description>&lt;p&gt;I recently needed something small: a &lt;code&gt;POST /api/contact&lt;/code&gt; endpoint for my portfolio site at &lt;code&gt;https://www.isaiah.name.ng&lt;/code&gt; that takes a name, email, and message, validates it, and emails it to me. The kind of thing you'd normally knock out without thinking twice.&lt;/p&gt;
&lt;p&gt;I decided to build it directly on my own &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/isaiahpeter/adding-openapi-support-to-mummy-a-nim-http-framework-227m&quot;&gt;Mummy fork&lt;/a&gt; — the one with OpenAPI schema generation, typed validation, and composable middleware — partly to actually dogfood it on something real, and partly because I assumed a single-endpoint service would be trivial. It mostly was. But three specific bugs along the way taught me more about the fork's internals than building the fork itself did.&lt;/p&gt;
&lt;h3 id=&quot;the-shape-of-the-service&quot;&gt;The Shape of the Service&lt;/h3&gt;
&lt;p&gt;Nothing exotic: a &lt;code&gt;ContactBody&lt;/code&gt; type with &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;message&lt;/code&gt;, all required strings. Validation happens in two layers — &lt;code&gt;parseValidatedBody[ContactBody]&lt;/code&gt; checks presence and type against the fork's &lt;code&gt;schemaOf&lt;/code&gt;-generated schema, and then explicit checks enforce non-empty fields, a 2000-character cap, and a hand-rolled email format check. Either layer raising the fork's &lt;code&gt;ValidationError&lt;/code&gt; gets mapped to a clean &lt;code&gt;400 {&quot;error&quot;: ...}&lt;/code&gt; by &lt;code&gt;installDefaultErrorHandler&lt;/code&gt;. A send failure is logged server-side and answered with a generic &lt;code&gt;500&lt;/code&gt; — the caller never sees why an email failed to send, only that it did.&lt;/p&gt;
&lt;p&gt;On top of that: CORS middleware that rejects disallowed origins with &lt;code&gt;403&lt;/code&gt; and answers preflight requests properly, an in-memory rate limiter capping submissions at 5 per IP per 10 minutes, and the fork's &lt;code&gt;loggingMiddleware&lt;/code&gt; as the outermost layer so every request logs method, path, and timing. The OpenAPI docs come free from the same schema the validator uses, served at &lt;code&gt;/docs&lt;/code&gt; with the spec at &lt;code&gt;/openapi.json&lt;/code&gt; — the whole reason I built the fork in the first place.&lt;/p&gt;
&lt;h3 id=&quot;the-pivot-nobody-plans-for-smtp-resend&quot;&gt;The Pivot Nobody Plans For: SMTP → Resend&lt;/h3&gt;
&lt;p&gt;The original plan was plain SMTP via &lt;code&gt;std/smtp&lt;/code&gt;. It compiled fine against the fork. Then I deployed to Render's free tier and every send just hung until timeout — Render blocks outbound SMTP ports (25/465/587) on that tier. Resend's REST API runs over HTTPS:443, which isn't blocked, and needs just one API key. As a bonus, its failures come back as ordinary HTTP errors the handler already catches and logs, instead of the occasional &lt;code&gt;AssertionDefect&lt;/code&gt; that a raw SMTP library can throw and potentially take a worker down with it.&lt;/p&gt;
&lt;p&gt;This is the kind of constraint you only discover by actually deploying, not by reading docs — worth remembering if you're building anything Render-hosted that needs to send mail.&lt;/p&gt;
&lt;h3 id=&quot;three-bugs-that-taught-me-more-than-the-feature-did&quot;&gt;Three Bugs That Taught Me More Than the Feature Did&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Two &lt;code&gt;HttpHeaders&lt;/code&gt; types that look identical and aren't.&lt;/strong&gt;&lt;br&gt;
Mummy imports &lt;code&gt;std/httpcore&lt;/code&gt; aliased internally, and &lt;code&gt;Request.headers&lt;/code&gt; is typed against that aliased import. My module also did a plain &lt;code&gt;import std/httpcore&lt;/code&gt; for the Resend payload types — and suddenly every header operation threw a &quot;type mismatch&quot; between what looked like the same type. Nim's module system treats the two imports as distinct type identities even though they're structurally the same. The fix was narrow: &lt;code&gt;import std/httpcore except HttpHeaders&lt;/code&gt;, so my module only pulled in the pieces it needed for the Resend payload and left the header type alone. This was, by a wide margin, the buggiest hour of the whole build — and a good reminder that &quot;same type, different import path&quot; is a real failure mode in Nim, not a theoretical one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;gcsafe&lt;/code&gt; doesn't care that your global never actually changes.&lt;/strong&gt;&lt;br&gt;
The fork's &lt;code&gt;Middleware&lt;/code&gt; and &lt;code&gt;RequestHandler&lt;/code&gt; types are marked &lt;code&gt;{.gcsafe.}&lt;/code&gt;, which means the compiler's data-race checker gets strict about any global state touched inside a handler — because Mummy dispatches requests across a worker-thread pool, not a single event loop. My CORS middleware read a &lt;code&gt;var&lt;/code&gt; global holding the allowed-origins list, set once at startup and never touched again — and the compiler didn't care that it was logically immutable after init. It only sees a mutable global read from a &lt;code&gt;gcsafe&lt;/code&gt; context. The fix was a &lt;code&gt;{.cast(gcsafe).}&lt;/code&gt; block around that specific read, while the genuinely-shared rate-limit table stayed behind a real &lt;code&gt;Lock&lt;/code&gt; — the same pattern the fork already uses internally for &lt;code&gt;bearerAuthMiddleware&lt;/code&gt;. It's a useful distinction to internalize on a threaded server: &quot;never mutated after startup&quot; and &quot;thread-safe&quot; are not the same claim, and the compiler will only ever check for the second one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. An unregistered route means your middleware never runs — including for the request that most needs it.&lt;/strong&gt;&lt;br&gt;
Browsers send an &lt;code&gt;OPTIONS&lt;/code&gt; preflight before certain cross-origin &lt;code&gt;POST&lt;/code&gt; requests. Mummy only runs its middleware chain for methods that have a registered route — so with no &lt;code&gt;OPTIONS /api/contact&lt;/code&gt; route, Mummy answered preflight with its own 405 before &lt;code&gt;corsMiddleware&lt;/code&gt; ever got a chance to run. The browser never saw the &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; header it needed, so the real &lt;code&gt;POST&lt;/code&gt; was blocked client-side despite the server being otherwise correctly configured. The fix was almost comically small: register an &lt;code&gt;OPTIONS /api/contact&lt;/code&gt; route whose handler does nothing but exist, which is enough to route the request into the middleware chain where &lt;code&gt;corsMiddleware&lt;/code&gt; can actually answer it with a &lt;code&gt;204&lt;/code&gt;. Easy to fix once you know it — genuinely confusing the first time a working-looking CORS setup silently fails only on the preflight leg.&lt;/p&gt;
&lt;h3 id=&quot;what-actually-got-verified&quot;&gt;What Actually Got Verified&lt;/h3&gt;
&lt;p&gt;Before calling it done, I ran it locally and checked the specific paths that tend to hide bugs rather than just the happy path: a disallowed origin (&lt;code&gt;403&lt;/code&gt;), a malformed email (&lt;code&gt;400&lt;/code&gt; with the exact field named), a name at the 2000-character boundary (passes) versus one character over (rejected), six rapid submissions from one IP (five succeed, the sixth comes back &lt;code&gt;429&lt;/code&gt; with &lt;code&gt;Retry-After&lt;/code&gt;), and that &lt;code&gt;/health&lt;/code&gt; and &lt;code&gt;OPTIONS&lt;/code&gt; both stay outside the rate limiter so Render's health checks and CORS preflight never get throttled.&lt;/p&gt;
&lt;h3 id=&quot;why-this-was-worth-writing-up&quot;&gt;Why This Was Worth Writing Up&lt;/h3&gt;
&lt;p&gt;None of these three bugs were about the feature — a contact form endpoint is about as unglamorous as backend work gets. They were about the specific intersection of Nim's module aliasing, &lt;code&gt;gcsafe&lt;/code&gt;'s literal (not logical) read of what's safe, and Mummy's routing-before-middleware order. If you're building anything on Mummy — my fork or the original — that touches CORS, thread-shared state, or has more than one import path into &lt;code&gt;std/httpcore&lt;/code&gt;, these three are worth checking for before you hit them the hard way.&lt;/p&gt;
&lt;p&gt;The full service, Dockerfile, and a drop-in &lt;code&gt;script.js&lt;/code&gt; for wiring this into any static contact form are in the repo if you want to see the whole thing end to end.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27202/a-tiny-nim-microservice-three-real-bugs-and-what-they-taught-me-about-my-own-mummy-fork</guid>
<pubDate>Tue, 15 Sep 2026 00:19:50 +0000</pubDate>
</item>
<item>
<title>Idempotency in APIs - Why Your Retry Logic Can Break Everything (And How to Fix It)</title>
<link>https://coderlegion.com/26972/idempotency-in-apis-why-your-retry-logic-can-break-everything-and-how-to-fix-it</link>
<description>&lt;p&gt;Modern applications rely heavily on APIs. But networks are unreliable - requests fail, time out, or get retried.&lt;/p&gt;
&lt;p&gt;Now imagine this scenario:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User clicks &lt;strong&gt;“Pay Now”&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Request sent to server&lt;/li&gt;
&lt;li&gt;Payment processed successfully&lt;/li&gt;
&lt;li&gt;But response is lost due to network issue&lt;/li&gt;
&lt;li&gt;Client retries the request&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;💥 Payment gets processed again. User is charged twice.&lt;/p&gt;
&lt;p&gt;This is one of the most dangerous and common problems in distributed systems.&lt;/p&gt;
&lt;p&gt;The solution? &lt;strong&gt;Idempotency.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-is-idempotency-&quot;&gt;What is Idempotency?&lt;/h2&gt;
&lt;p&gt;An operation is &lt;strong&gt;idempotent&lt;/strong&gt; if performing it multiple times produces the same result as performing it once.&lt;/p&gt;
&lt;p&gt;In simple terms:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Same request → same effect → no duplicate side effects&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Create Order (without idempotency):
Request sent twice → 2 orders created ❌

Create Order (with idempotency):
Request sent twice → only 1 order created ✅
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Idempotency ensures system correctness even when retries happen.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-do-retries-happen-&quot;&gt;Why Do Retries Happen?&lt;/h2&gt;
&lt;p&gt;Retries are extremely common due to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Network timeouts&lt;/li&gt;
&lt;li&gt;Server crashes&lt;/li&gt;
&lt;li&gt;Client retries&lt;/li&gt;
&lt;li&gt;Load balancers retrying requests&lt;/li&gt;
&lt;li&gt;Mobile networks instability&lt;/li&gt;
&lt;li&gt;API gateway retries&lt;/li&gt;
&lt;li&gt;Reverse proxy retries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Retries are normal. Duplicate side effects are not.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;which-http-methods-are-idempotent-&quot;&gt;Which HTTP Methods Are Idempotent?&lt;/h2&gt;
&lt;p&gt;According to HTTP specification:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Method &lt;/th&gt;&lt;th&gt;Idempotent &lt;/th&gt;&lt;th&gt;Explanation&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;GET    &lt;/td&gt;&lt;td&gt;✅ Yes      &lt;/td&gt;&lt;td&gt;Fetch data&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PUT    &lt;/td&gt;&lt;td&gt;✅ Yes      &lt;/td&gt;&lt;td&gt;Replace resource&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DELETE &lt;/td&gt;&lt;td&gt;✅ Yes      &lt;/td&gt;&lt;td&gt;Delete resource&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;POST   &lt;/td&gt;&lt;td&gt;❌ No       &lt;/td&gt;&lt;td&gt;Create resource&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PATCH  &lt;/td&gt;&lt;td&gt;❌ No       &lt;/td&gt;&lt;td&gt;Partial update&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;POST is NOT idempotent by default.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /orders
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Calling twice → creates 2 orders.&lt;/p&gt;
&lt;p&gt;This must be handled explicitly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;real-world-example-payment-api-problem&quot;&gt;Real-World Example: Payment API Problem&lt;/h2&gt;
&lt;p&gt;Client sends:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /payments
{
  &quot;user_id&quot;: &quot;123&quot;,
  &quot;amount&quot;: 100
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If client retries due to timeout:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /payments
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without idempotency → duplicate payment ❌&lt;/p&gt;
&lt;p&gt;With idempotency → single payment ✅&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;solution-idempotency-key&quot;&gt;Solution: Idempotency Key&lt;/h2&gt;
&lt;p&gt;Client sends a unique key with request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /payments
Idempotency-Key: abc123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Server behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If key is new → process request&lt;/li&gt;
&lt;li&gt;If key already exists → return previous result&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This guarantees single execution.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-idempotency-works-internally&quot;&gt;How Idempotency Works Internally&lt;/h2&gt;
&lt;p&gt;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client generates unique key&lt;/li&gt;
&lt;li&gt;Sends request with key&lt;/li&gt;
&lt;li&gt;Server checks database/cache for key&lt;/li&gt;
&lt;li&gt;If key exists → return stored response&lt;/li&gt;
&lt;li&gt;If not exists → process request&lt;/li&gt;
&lt;li&gt;Store key and response&lt;/li&gt;
&lt;li&gt;Return response&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&quot;go-implementation-example&quot;&gt;Go Implementation Example&lt;/h2&gt;
&lt;p&gt;Let’s implement idempotent payment API using Go.&lt;/p&gt;
&lt;h3 id=&quot;database-table&quot;&gt;Database Table&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;idempotency_keys table:

key (primary key)
response
created_at
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;go-example-basic-version-&quot;&gt;Go Example (Basic Version)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type IdempotencyRecord struct {
    Key      string
    Response string
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;handler-implementation&quot;&gt;Handler Implementation&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func PaymentHandler(w http.ResponseWriter, r *http.Request) {

    key := r.Header.Get(&quot;Idempotency-Key&quot;)

    if key == &quot;&quot; {
        http.Error(w, &quot;Idempotency-Key required&quot;, http.StatusBadRequest)
        return
    }

    // Check if key exists
    record, exists := getIdempotencyRecord(key)

    if exists {
        w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)
        w.Write([]byte(record.Response))
        return
    }

    // Process payment
    paymentID := processPayment()

    response := fmt.Sprintf(`{&quot;payment_id&quot;: &quot;%s&quot;}`, paymentID)

    // Store key and response
    saveIdempotencyRecord(key, response)

    w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)
    w.Write([]byte(response))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;example-flow&quot;&gt;Example Flow&lt;/h2&gt;
&lt;p&gt;First request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /payments
Idempotency-Key: abc123

Response:
{
  &quot;payment_id&quot;: &quot;pay_001&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Retry request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /payments
Idempotency-Key: abc123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Response returned from storage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;payment_id&quot;: &quot;pay_001&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No duplicate payment created.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;production-implementation-using-redis-recommended-&quot;&gt;Production Implementation Using Redis (Recommended)&lt;/h2&gt;
&lt;p&gt;Redis is ideal because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extremely fast&lt;/li&gt;
&lt;li&gt;Supports expiration (TTL)&lt;/li&gt;
&lt;li&gt;Perfect for temporary idempotency storage&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;redis-example-in-go&quot;&gt;Redis Example in Go&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func PaymentHandler(w http.ResponseWriter, r *http.Request) {

    key := r.Header.Get(&quot;Idempotency-Key&quot;)

    val, err := redisClient.Get(ctx, key).Result()

    if err == nil {
        w.Write([]byte(val))
        return
    }

    paymentID := processPayment()

    response := fmt.Sprintf(`{&quot;payment_id&quot;: &quot;%s&quot;}`, paymentID)

    redisClient.Set(ctx, key, response, time.Hour*24)

    w.Write([]byte(response))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;important-handle-race-conditions&quot;&gt;Important: Handle Race Conditions&lt;/h2&gt;
&lt;p&gt;Two requests may arrive simultaneously with same key.&lt;/p&gt;
&lt;p&gt;Solution: Use atomic operations.&lt;/p&gt;
&lt;p&gt;Redis example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SET key value NX
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NX = set only if not exists&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;important-best-practice-store-full-response&quot;&gt;Important Best Practice: Store Full Response&lt;/h2&gt;
&lt;p&gt;Do not just store key.&lt;/p&gt;
&lt;p&gt;Store:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;status code&lt;/li&gt;
&lt;li&gt;response body&lt;/li&gt;
&lt;li&gt;timestamp&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because retry must return exact same response.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;example-database-schema-postgresql-&quot;&gt;Example Database Schema (PostgreSQL)&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE idempotency_keys (
    key TEXT PRIMARY KEY,
    response JSONB,
    status_code INT,
    created_at TIMESTAMP DEFAULT NOW()
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Primary key ensures uniqueness.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;real-world-systems-that-use-idempotency&quot;&gt;Real-World Systems That Use Idempotency&lt;/h2&gt;
&lt;p&gt;Critical systems use idempotency extensively:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Payment APIs&lt;/li&gt;
&lt;li&gt;Order creation APIs&lt;/li&gt;
&lt;li&gt;Financial transactions&lt;/li&gt;
&lt;li&gt;Booking systems&lt;/li&gt;
&lt;li&gt;Wallet transfers&lt;/li&gt;
&lt;li&gt;Stripe payments&lt;/li&gt;
&lt;li&gt;Banking APIs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without idempotency, financial systems would break.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;common-mistake-developers-make&quot;&gt;Common Mistake Developers Make&lt;/h2&gt;
&lt;p&gt;❌ Using retry without idempotency&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;retry 3 times on failure
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This multiplies side effects.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Retry logic: 3 times
Result: 3 payments
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dangerous bug.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;idempotency-vs-duplicate-prevention-using-unique-constraints&quot;&gt;Idempotency vs Duplicate Prevention Using Unique Constraints&lt;/h2&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UNIQUE(user_id, order_id)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This prevents duplicates at DB level.&lt;/p&gt;
&lt;p&gt;But idempotency is better because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Works at API level&lt;/li&gt;
&lt;li&gt;Handles retries cleanly&lt;/li&gt;
&lt;li&gt;Returns same response&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Best approach: use BOTH.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;when-should-you-use-idempotency-&quot;&gt;When Should You Use Idempotency?&lt;/h2&gt;
&lt;p&gt;Use idempotency for operations that create side effects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Payment creation&lt;/li&gt;
&lt;li&gt;Order creation&lt;/li&gt;
&lt;li&gt;Wallet transfer&lt;/li&gt;
&lt;li&gt;Booking&lt;/li&gt;
&lt;li&gt;Resource creation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not required for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GET requests&lt;/li&gt;
&lt;li&gt;Read-only operations&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-client-should-generate-idempotency-key&quot;&gt;How Client Should Generate Idempotency Key&lt;/h2&gt;
&lt;p&gt;Use UUID:&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;550e8400-e29b-41d4-a716-446655440000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;uuid.New().String()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unique per operation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;idempotency-key-expiration-strategy&quot;&gt;Idempotency Key Expiration Strategy&lt;/h2&gt;
&lt;p&gt;Keys should expire after some time.&lt;/p&gt;
&lt;p&gt;Common TTL:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;24 hours&lt;/li&gt;
&lt;li&gt;48 hours&lt;/li&gt;
&lt;li&gt;7 days (depending on business need)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Redis makes expiration easy.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;example-architecture&quot;&gt;Example Architecture&lt;/h2&gt;
&lt;p&gt;Client → API Gateway → Service → Redis → Database&lt;/p&gt;
&lt;p&gt;Redis handles idempotency keys&lt;br&gt;
Database handles actual data&lt;/p&gt;
&lt;p&gt;Fast and reliable.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;real-production-scenario-mobile-network-retry&quot;&gt;Real Production Scenario: Mobile Network Retry&lt;/h2&gt;
&lt;p&gt;Mobile user taps &quot;Place Order&quot;&lt;/p&gt;
&lt;p&gt;Network slow → timeout&lt;br&gt;
Client retries automatically&lt;/p&gt;
&lt;p&gt;Without idempotency → duplicate orders&lt;/p&gt;
&lt;p&gt;With idempotency → safe retry&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;important-difference-safe-retry-vs-unsafe-retry&quot;&gt;Important Difference: Safe Retry vs Unsafe Retry&lt;/h2&gt;
&lt;p&gt;Unsafe retry:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /orders
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Safe retry:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /orders
Idempotency-Key: unique-key
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;idempotency-vs-exactly-once-execution&quot;&gt;Idempotency vs Exactly-Once Execution&lt;/h2&gt;
&lt;p&gt;Exactly-once execution is impossible in distributed systems.&lt;/p&gt;
&lt;p&gt;Idempotency provides practical solution.&lt;/p&gt;
&lt;p&gt;It ensures same final state.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;best-practices-checklist&quot;&gt;Best Practices Checklist&lt;/h2&gt;
&lt;p&gt;Always:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use idempotency keys for POST APIs&lt;/li&gt;
&lt;li&gt;Store keys in Redis or database&lt;/li&gt;
&lt;li&gt;Store full response&lt;/li&gt;
&lt;li&gt;Use expiration&lt;/li&gt;
&lt;li&gt;Handle race conditions&lt;/li&gt;
&lt;li&gt;Use UUID keys&lt;/li&gt;
&lt;li&gt;Combine with database constraints&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;key-takeaway&quot;&gt;Key Takeaway&lt;/h2&gt;
&lt;p&gt;Retries are normal.&lt;/p&gt;
&lt;p&gt;Duplicate side effects are dangerous.&lt;/p&gt;
&lt;p&gt;Idempotency ensures your APIs remain safe, reliable, and production-ready.&lt;/p&gt;
&lt;p&gt;It is not optional for critical APIs - it is essential.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thought&quot;&gt;Final Thought&lt;/h2&gt;
&lt;p&gt;If your API handles payments, orders, bookings, or financial transactions - idempotency is mandatory.&lt;/p&gt;
&lt;p&gt;Without it, retries can silently corrupt your system.&lt;/p&gt;
&lt;p&gt;With it, your APIs become reliable, predictable, and safe.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26972/idempotency-in-apis-why-your-retry-logic-can-break-everything-and-how-to-fix-it</guid>
<pubDate>Sat, 12 Sep 2026 13:30:46 +0000</pubDate>
</item>
<item>
<title>Don't Let One Broken Service Crash Your Whole App: The Circuit Breaker Pattern</title>
<link>https://coderlegion.com/25249/dont-let-one-broken-service-crash-your-whole-app-the-circuit-breaker-pattern</link>
<description>&lt;h3 id=&quot;what-is-the-circuit-breaker-pattern-&quot;&gt;What is the Circuit Breaker Pattern?&lt;/h3&gt;
&lt;p&gt;The Circuit Breaker Pattern is an architectural design pattern used in software development to detect failures and encapsulate the logic of preventing a failure from cascading throughout a system. It works by wrapping a sensitive network call or database query in a monitoring object that tracks recent failures. When those failures exceed a pre-defined threshold, the breaker trips, automatically blocking subsequent requests to prevent further damage. This prevents a system from repeatedly executing an operation that is almost guaranteed to fail, protecting both the client and the struggling service.&lt;/p&gt;
&lt;h3 id=&quot;a-real-life-analogy-the-household-fuse-box&quot;&gt;A Real-Life Analogy: The Household Fuse Box&lt;/h3&gt;
&lt;p&gt;Imagine you are in your kitchen on a weekend morning. You decide to toast some bread, but your toaster has an internal short circuit. When you push down the lever, instead of just warming up, the faulty appliance starts drawing an unsafe, massive surge of electricity. Without a safety device in place, the copper wiring in your kitchen walls would quickly overheat, melt its protective insulation, and potentially start a devastating house fire.&lt;/p&gt;
&lt;p&gt;Fortunately, your home features an electrical circuit breaker panel. The breaker detects this dangerous spike in electrical current and instantly trips, cutting off the flow of electricity to the kitchen outlets. The toaster is dead, but your home is completely safe because the danger was isolated. To fix things, you unplug the toaster, run to the garage, and flip the breaker switch back to its original position to restore power to the remaining kitchen appliances.&lt;/p&gt;
&lt;h3 id=&quot;why-it-matters-in-daily-tech-operations&quot;&gt;Why It Matters in Daily Tech Operations&lt;/h3&gt;
&lt;p&gt;In modern software, apps are rarely self-contained. They rely on microservices—smaller, interconnected applications—and external APIs (Application Programming Interfaces) to run. For instance, an e-commerce platform relies on a product catalog service, a payment gateway, and an email service.&lt;/p&gt;
&lt;p&gt;If the payment gateway slows down or crashes during a sale, what happens? Without a circuit breaker, every customer trying to checkout will wait indefinitely. Each waiting user consumes system resources, such as memory and web threads, on your server. As more users click checkout, these resources quickly exhaust. Within minutes, your core checkout service crashes under the pressure, which then causes the inventory service to crash, leading to a complete system outage.&lt;/p&gt;
&lt;p&gt;By implementing the Circuit Breaker Pattern, engineers can safeguard their systems. Once the circuit breaker detects that the payment gateway is failing, it immediately trips. New checkout requests instantly receive a friendly message like, &quot;Our payment gateway is busy, please try again in a moment.&quot; This spares your servers from waiting on a dead service, allowing the rest of your website to remain online and functional.&lt;/p&gt;
&lt;h3 id=&quot;the-concept-in-action&quot;&gt;The Concept in Action&lt;/h3&gt;
&lt;p&gt;Here is a simple, lightweight implementation of a circuit breaker in JavaScript to show how this logic looks under the hood:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class SimpleCircuitBreaker {
  constructor(requestFunction, failureThreshold = 3, cooldownMs = 10000) {
    this.requestFunction = requestFunction;
    this.failureThreshold = failureThreshold;
    this.cooldownMs = cooldownMs;
    this.state = &quot;CLOSED&quot;;
    this.failures = 0;
    this.nextAttemptTime = 0;
  }

  async execute() {
    if (this.state === &quot;OPEN&quot;) {
      if (Date.now() &amp;gt; this.nextAttemptTime) {
        this.state = &quot;HALF-OPEN&quot;;
      } else {
        throw new Error(&quot;Circuit is OPEN. Request blocked for safety.&quot;);
      }
    }

    try {
      const result = await this.requestFunction();
      this.reset();
      return result;
    } catch (error) {
      this.handleFailure();
      throw error;
    }
  }

  reset() {
    this.state = &quot;CLOSED&quot;;
    this.failures = 0;
  }

  handleFailure() {
    this.failures++;
    if (this.failures &amp;gt;= this.failureThreshold) {
      this.state = &quot;OPEN&quot;;
      this.nextAttemptTime = Date.now() + this.cooldownMs;
      console.log(&quot;Circuit breaker tripped! Blocking future requests.&quot;);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-takeaway-1&quot;&gt;The Takeaway&lt;/h3&gt;
&lt;p&gt;Building highly reliable modern software systems is not about pretending that failures will never occur; it is about managing those failures gracefully when they inevitably do. The Circuit Breaker Pattern shifts our architectural strategy from fragile, hopeful connections to defensive, self-healing resilience. By failing fast, your application protects its precious memory and computing power, preventing localized outages from snowballing into catastrophic system-wide downtime.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;resources&quot;&gt;Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Saurav-TB-Pandey/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;react-hook-lab:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect with me on LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/pandeysaurav/&quot;&gt;Saurav Pandey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/08/how-to-build-resilient-apps-with.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/saurav_tb_pandey/dont-let-one-broken-service-crash-your-whole-app-the-circuit-breaker-pattern-12j7&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25249/dont-let-one-broken-service-crash-your-whole-app-the-circuit-breaker-pattern</guid>
<pubDate>Wed, 09 Sep 2026 08:07:41 +0000</pubDate>
</item>
<item>
<title>Don't Let Your Microservices Burn: Understanding the Circuit Breaker Pattern</title>
<link>https://coderlegion.com/26194/dont-let-your-microservices-burn-understanding-the-circuit-breaker-pattern</link>
<description>&lt;p&gt;In modern software engineering, building a reliable system isn't just about preventing errors; it is about managing them gracefully when they occur. The &lt;strong&gt;Circuit Breaker Pattern&lt;/strong&gt; is a design pattern used in software development to prevent an application from repeatedly trying to execute an operation that is highly likely to fail. Instead of wasting resources on doomed requests, the system temporarily blocks them, allowing the failing service time to recover. Once the service is healthy again, the block is lifted and normal operations resume.&lt;/p&gt;
&lt;h4 id=&quot;a-real-world-analogy-the-household-breaker&quot;&gt;A Real-World Analogy: The Household Breaker&lt;/h4&gt;
&lt;p&gt;Think of a household electrical circuit breaker. If a faulty microwave draws too much current, the circuit breaker trips, instantly shutting off electricity to that part of the house. This prevents the wires from overheating and catching fire. Instead of continuously feeding dangerous levels of electricity into a broken appliance, the breaker pauses the flow. It stays open until you unplug the bad appliance and reset the switch.&lt;/p&gt;
&lt;p&gt;In software, the concept is identical. When a third-party API or server becomes slow or non-responsive, our virtual circuit breaker trips. It stops sending traffic to that broken service, saving the rest of our application from catching fire and crashing due to backlogged requests.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters-in-tech&quot;&gt;Why It Matters in Tech&lt;/h4&gt;
&lt;p&gt;In modern cloud architectures, systems constantly communicate with each other. If a database or a payment gateway goes down, and your application keeps hammering it with thousands of requests per second, you will exhaust your own server's memory and database connection pools. This causes a &quot;cascading failure&quot; where your entire platform goes offline because of one minor, non-critical dependency.&lt;/p&gt;
&lt;p&gt;Engineers use circuit breakers to &quot;fail fast.&quot; Instead of letting a user wait 30 seconds for a timeout, the circuit breaker immediately throws an error or returns cached data. This protects downstream systems from being crushed while they are struggling, and keeps your primary application fast and responsive for other tasks.&lt;/p&gt;
&lt;h4 id=&quot;the-pattern-in-action&quot;&gt;The Pattern in Action&lt;/h4&gt;
&lt;p&gt;Here is a simple JavaScript implementation of a state-based circuit breaker. It wraps an API call and tracks failures:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class CircuitBreaker {
  constructor(requestFunction, failureThreshold, recoveryTime) {
    this.request = requestFunction;
    this.failureThreshold = failureThreshold;
    this.recoveryTime = recoveryTime;
    this.state = &quot;CLOSED&quot;; // CLOSED, OPEN, HALF-OPEN
    this.failures = 0;
    this.nextAttempt = Date.now();
  }

  async execute(...args) {
    if (this.state === &quot;OPEN&quot;) {
      if (Date.now() &amp;gt; this.nextAttempt) {
        this.state = &quot;HALF-OPEN&quot;;
      } else {
        throw new Error(&quot;Circuit is OPEN. Request blocked.&quot;);
      }
    }

    try {
      const result = await this.request(...args);
      this.reset();
      return result;
    } catch (error) {
      this.recordFailure();
      throw error;
    }
  }

  reset() {
    this.state = &quot;CLOSED&quot;;
    this.failures = 0;
  }

  recordFailure() {
    this.failures++;
    if (this.failures &amp;gt;= this.failureThreshold || this.state === &quot;HALF-OPEN&quot;) {
      this.state = &quot;OPEN&quot;;
      this.nextAttempt = Date.now() + this.recoveryTime;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this code, the breaker starts as &lt;code&gt;CLOSED&lt;/code&gt; (operational). If the nested request fails too many times, the state flips to &lt;code&gt;OPEN&lt;/code&gt;, blocking further attempts immediately. After the recovery time passes, it allows a single test request (&lt;code&gt;HALF-OPEN&lt;/code&gt;) to see if the dependency is back online. If it succeeds, the breaker resets to &lt;code&gt;CLOSED&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;the-takeaway-2&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;The Circuit Breaker Pattern shifts our engineering mindset from trying to prevent all failures to gracefully managing them when they inevitably happen. By isolating broken parts of a system, you ensure that a single slow API call doesn't drag down your entire product, keeping your user experience stable even during partial outages.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;resources-1&quot;&gt;Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Saurav-TB-Pandey/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;react-hook-lab:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect with me on LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/pandeysaurav/&quot;&gt;Saurav Pandey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/building-resilient-software-beginners.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26194/dont-let-your-microservices-burn-understanding-the-circuit-breaker-pattern</guid>
<pubDate>Fri, 04 Sep 2026 08:20:08 +0000</pubDate>
</item>
<item>
<title>Service Mesh Zero-Trust Architecture</title>
<link>https://coderlegion.com/26024/service-mesh-zero-trust-architecture</link>
<description>&lt;h2 id=&quot;service-mesh-zero-trust-architecture&quot;&gt;Service Mesh Zero-Trust Architecture&lt;/h2&gt;
&lt;p&gt;Zero-trust is a property that emerges when several independent checks each verify a request on their own, without assuming the layers around them already did the work. It often gets described as a product you install or a setting you switch on, which badly undersells how much has to line up for it to actually hold.&lt;/p&gt;
&lt;p&gt;I learned that building an HR directory designed to be attacked. The rule I wanted it to hold was simple: if any single pod gets compromised, it still can't read data it has no business touching. Salaries, bank details, device serial numbers, the things an attacker would actually go after.&lt;/p&gt;
&lt;p&gt;No single mechanism enforces that rule. mTLS proves which service is on the other end of a connection, but it has nothing to say about whether that service should be making the call. A signed identity token establishes who the user is, yet a service that trusts raw headers will believe whatever an attacker sends it. Authorization decides what a user is allowed to do, and then an application bug skips the check and the database returns the whole table anyway. Every layer has a blind spot, and the thing that covers it is another layer that doesn't share the same one.&lt;/p&gt;
&lt;p&gt;So the architecture is a stack: every hop independently verified, every request authenticated and authorized, each layer built on the assumption that the one before it might fail. Remove a single layer and the others are left exposed in the exact spots they were relying on it to cover.&lt;/p&gt;
&lt;p&gt;This post is the architecture overview for the series: the application, the five security layers, how they compose into a single request flow, and what specifically breaks when you remove one. It all runs end-to-end on Istio on a local Kind cluster with real config. Later posts go deep into the YAML, Python, and policy files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What you need to know going in&lt;/strong&gt;: Istio is a service mesh for Kubernetes. The key idea is that every pod gets an Envoy sidecar proxy. All inbound and outbound traffic passes through the sidecar, which means identity verification, routing rules, and policy enforcement happen at the infrastructure layer before your application code sees the request. If you understand containers and basic networking, the rest will make sense as we go.&lt;/p&gt;
&lt;h3 id=&quot;the-application&quot;&gt;The Application&lt;/h3&gt;
&lt;p&gt;The domain is deliberately simple: an HR Employee Directory. Five microservices, a few database tables, a handful of roles. The security architecture is the interesting part. In the demo there are 4 roles used.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alice (employee)&lt;/strong&gt; — on her own profile she sees full PII and exact salary; everyone else only gets name, title, and department.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mary (manager)&lt;/strong&gt; — on Alice (her direct report) she sees salary &lt;strong&gt;band&lt;/strong&gt;, not exact pay or SSN; on others, same public view as any employee.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Henry (HR admin)&lt;/strong&gt; — full HR fields on all employees (salary, SSN, etc.), but &lt;strong&gt;no&lt;/strong&gt; full IT asset serial numbers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ivan (IT admin)&lt;/strong&gt; — only basic employee fields everywhere, but &lt;strong&gt;full&lt;/strong&gt; hardware/asset details including serials.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/uploads/covers/6a0c37b1fd930acd9e15653e/7b4c9e3e-c16e-4563-972a-5b7ad94c85ea.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Profile Aggregator&lt;/strong&gt; (&lt;code&gt;ms1-profile-aggregator&lt;/code&gt;) is a fan-out orchestrator that calls Employee Records and Device Inventory, merges the results, and returns a single profile response. Gateway-facing, requires authentication.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Employee Records&lt;/strong&gt; (&lt;code&gt;ms2-employee-details&lt;/code&gt;) is the source of truth for employee records, PII, and financial data (salary, bank details). Internal only, not directly reachable from outside.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Device Inventory&lt;/strong&gt; (&lt;code&gt;ms3-hardware-assets&lt;/code&gt;) tracks which devices are assigned to which employees (serial numbers, MAC addresses). Internal only, same as Employee Records.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Holiday Calendar&lt;/strong&gt; (&lt;code&gt;ms4-holiday-calendar&lt;/code&gt;) handles company holidays. Gateway-facing, requires authentication. Read by everyone, written by HR.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Office Directory&lt;/strong&gt; (&lt;code&gt;ms5-office-locations&lt;/code&gt;) serves office location data. Gateway-facing, no authentication required for reads. The only fully public endpoint in the system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Six roles : &lt;code&gt;employee&lt;/code&gt;, &lt;code&gt;manager&lt;/code&gt;, &lt;code&gt;hr_admin&lt;/code&gt;, &lt;code&gt;it_admin&lt;/code&gt;, &lt;code&gt;public_data_admin&lt;/code&gt;, and &lt;code&gt;security_auditor&lt;/code&gt;. The first four drive the HR/profile flow and are the focus of this series. &lt;code&gt;public_data_admin&lt;/code&gt; manages office and holiday data, and &lt;code&gt;security_auditor&lt;/code&gt; has read-only access for compliance. The point that matters for security: each role sees a different slice of data from the same endpoints, controlled entirely by policy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/uploads/covers/6a0c37b1fd930acd9e15653e/0f6c3247-50ed-4801-9974-13a1e6007d2a.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The tiering matters for security:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tier 1&lt;/strong&gt; (gateway-facing): Profile Aggregator, Holiday Calendar, Office Directory. Reachable from the Istio ingress gateway.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tier 2&lt;/strong&gt; (internal only): Employee Records, Device Inventory. Reachable exclusively from Profile Aggregator. No direct external path exists.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tier 3&lt;/strong&gt; (data): PostgreSQL. Reachable only from specific service identities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Holiday Calendar and Office Directory cannot reach Employee Records or Device Inventory regardless of what code runs inside them. Profile Aggregator also cannot connect directly to PostgreSQL. The mesh intercepts every connection attempt and checks whether the calling service is allowed to talk to the destination. If it's not on the allow-list, the connection is rejected before any application code even sees the request.&lt;/p&gt;
&lt;p&gt;An attacker who compromises Holiday Calendar cannot reach Employee Records. The network topology itself is a security boundary, enforced by &lt;code&gt;Istio&lt;/code&gt; &lt;code&gt;AuthorizationPolicy&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-threat-model&quot;&gt;The Threat Model&lt;/h3&gt;
&lt;p&gt;Every security layer exists because of a specific assumption about what can go wrong. These are the threats we're designing against:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An external user may send arbitrary headers, including internal trust headers, in their HTTP request.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Any single application pod may be compromised and act maliciously within whatever network access it has.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Application code may have bugs: missing authorization checks, overly broad queries, endpoints that skip validation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Database queries may return more data than intended if the application doesn't filter correctly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Signing keys and secrets must not live inside application pods, because any compromised pod leaks whatever it holds.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each layer in the architecture addresses one or more of these. When we get to &quot;What Happens When You Skip a Layer,&quot; you can map each failure directly back to this list.&lt;/p&gt;
&lt;h3 id=&quot;the-layers&quot;&gt;The Layers&lt;/h3&gt;
&lt;p&gt;Five distinct security layers, each addressing an attack surface the others can't cover.&lt;/p&gt;
&lt;h4 id=&quot;1-transport-security-mtls-&quot;&gt;1. Transport Security (mTLS)&lt;/h4&gt;
&lt;p&gt;Mutual TLS encrypts data in transit and verifies the identity of both ends of the connection. Every pod in the mesh presents a SPIFFE identity certificate, and the receiving pod validates it before accepting any bytes.&lt;/p&gt;
&lt;p&gt;This gives you encrypted communication and peer identity verification. A rogue service without a valid mesh certificate cannot establish a connection. But mTLS has a specific scope: it verifies that Service A &lt;em&gt;is&lt;/em&gt; Service A. It does not determine whether Service A is &lt;em&gt;allowed&lt;/em&gt; to call Service B with this particular payload for this particular user. That's a different problem entirely.&lt;/p&gt;
&lt;h4 id=&quot;2-identity-propagation-signed-mesh-tokens-&quot;&gt;2. Identity Propagation (Signed Mesh Tokens)&lt;/h4&gt;
&lt;p&gt;Once a request enters the mesh and passes authentication at the gateway, the system needs to carry the user's verified identity through every downstream hop. A request from Alice hits Profile Aggregator, which then fans out to Employee Records and Device Inventory. Both downstream services need to know it's Alice, what role she holds, and that the request is legitimate.&lt;/p&gt;
&lt;p&gt;This is done via a cryptographically signed JWT, the &lt;code&gt;x-mesh-identity&lt;/code&gt; token, minted by Auth Service (&lt;code&gt;auth-service&lt;/code&gt;) and signed by Vault Transit. The token carries who the user is (&lt;code&gt;sub&lt;/code&gt;), what roles they hold (&lt;code&gt;roles_csv&lt;/code&gt;), which services this token is valid for (&lt;code&gt;aud&lt;/code&gt;), and who is acting on behalf of the user (&lt;code&gt;act&lt;/code&gt;, for service-to-service delegation).&lt;/p&gt;
&lt;p&gt;Any downstream service can verify this token using the public key from the Auth Service JWKS endpoint. No callback to Auth Service needed. But the token alone isn't sufficient. If projected headers like &lt;code&gt;x-ms2-user&lt;/code&gt; arrive from outside the mesh without being stripped, a service reading those headers trusts attacker-controlled values. That's why header stripping at the gateway is non-negotiable, and why each destination sidecar also strips and re-projects these headers from the verified JWT before the request reaches the application.&lt;/p&gt;
&lt;h4 id=&quot;3-request-authorization-policy-enforcement-&quot;&gt;3. Request Authorization (Policy Enforcement)&lt;/h4&gt;
&lt;p&gt;Knowing who someone is doesn't mean they're allowed to do what they're asking. Authorization in this architecture operates at four granularities, and each one catches what the layer above misses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Network-level:&lt;/strong&gt; Can this pod even reach that pod? Istio &lt;code&gt;AuthorizationPolicy&lt;/code&gt; enforces which service identities are allowed to initiate connections.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Request-level:&lt;/strong&gt; Is this user, with this role, allowed to call this endpoint? The &lt;code&gt;ExtAuthz&lt;/code&gt; check at the gateway applies coarse route-level decisions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resource-level:&lt;/strong&gt; Can this manager see &lt;em&gt;this specific employee's&lt;/em&gt; record? Cerbos evaluates fine-grained, context-aware policies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Field-level:&lt;/strong&gt; Can this role see salary data, or should it be masked? Cerbos again, with policies that return which fields to include per role.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Why four layers instead of one? Because a single policy engine can't efficiently handle all of these. Network-level happens at the sidecar before any application code runs. Field-level requires knowing the actual resource attributes at query time. Different layers, different information available, different enforcement points.&lt;/p&gt;
&lt;h4 id=&quot;4-data-access-security-row-level-security-&quot;&gt;4. Data Access Security (Row-Level Security)&lt;/h4&gt;
&lt;p&gt;The database is the last line of defense. PostgreSQL Row-Level Security policies enforce visibility at the engine level based on the current transaction context. Even if application code has a bug, a missing authorization check or a broken filter, RLS ensures the database itself drops rows the user shouldn't see.&lt;/p&gt;
&lt;p&gt;Application developers make mistakes. A query like &lt;code&gt;SELECT * FROM employees&lt;/code&gt; hitting production without a proper WHERE clause should still not leak the entire company directory. RLS makes that guarantee at the database layer, independent of whatever the application did or didn't check.&lt;/p&gt;
&lt;p&gt;There is a trust boundary here: the application sets &lt;code&gt;app.current_user_id&lt;/code&gt; on the transaction, and RLS uses that value. If the application sets it incorrectly, RLS enforces the wrong identity. In normal operation, the identity comes from sidecar-projected headers, so the application is a pass-through, not a source. But RLS does not protect against a fully compromised application process that sets arbitrary transaction context. RLS catches bugs (missing filters, broad queries). It does not stop a malicious process that controls its own DB session. The defense against that scenario is the layers above: AuthorizationPolicy limiting which pods can reach the database, scoped DB roles limiting which tables they can query, and Vault ensuring compromised pods can't escalate signing privileges.&lt;/p&gt;
&lt;h4 id=&quot;5-secrets-management-vault-&quot;&gt;5. Secrets Management (Vault)&lt;/h4&gt;
&lt;p&gt;Secrets in pods are the most common attack surface in Kubernetes. Environment variables, mounted files, ConfigMaps. Assume any compromised pod leaks whatever secrets it holds.&lt;/p&gt;
&lt;p&gt;In this architecture, Vault serves a specific critical function: the Transit engine signs mesh identity tokens without ever exposing the private key to any service. Auth Service sends bytes to Vault, Vault signs them, returns the signature. The private key is never exposed to Auth Service or any application pod. If Auth Service is compromised, the attacker can ask Vault to sign things (until you revoke access), but they never get the key itself. They can't take it offline and mint tokens indefinitely.&lt;/p&gt;
&lt;p&gt;Beyond signing, Vault is the natural home for database credentials, API keys, TLS certificates, and anything that would otherwise be scattered across pod specs. The pattern matters more than our specific usage: services request secrets through controlled APIs rather than holding them in memory at rest.&lt;/p&gt;
&lt;h3 id=&quot;how-the-layers-compose&quot;&gt;How the Layers Compose&lt;/h3&gt;
&lt;p&gt;These layers form a chain where each one depends on the guarantees of the ones around it. Here's what happens when a request enters the system.&lt;/p&gt;
&lt;p&gt;An external request hits the Istio Gateway. TLS terminates. Before anything else, an EnvoyFilter strips all internal trust headers (&lt;code&gt;x-mesh-identity&lt;/code&gt;, &lt;code&gt;x-ms*-user&lt;/code&gt;, &lt;code&gt;x-role-*&lt;/code&gt;), ensuring nothing spoofable survives from outside. This is the single most important security mechanism in the entire architecture. Without it, everything downstream can be bypassed by setting a header.&lt;/p&gt;
&lt;p&gt;The gateway's ExtAuthz filter calls Auth Service. Auth Service validates either the session cookie (opaque, database-backed) or the Bearer token (validated against Keycloak's JWKS). If valid, it calls Vault Transit to sign a short-lived mesh identity token with a 5 minute TTL. This token comes back to Envoy as a response header and gets injected into the request.&lt;/p&gt;
&lt;p&gt;Envoy routes the request to the target service. At the destination, a &lt;code&gt;RequestAuthentication&lt;/code&gt; resource validates the &lt;code&gt;x-mesh-identity&lt;/code&gt; token against the Auth Service JWKS endpoint. Istio's native &lt;code&gt;outputClaimToHeaders&lt;/code&gt; projects the validated claims into service-specific headers (&lt;code&gt;x-ms2-user&lt;/code&gt;, &lt;code&gt;x-ms2-role&lt;/code&gt;). The service never touches the JWT directly. It just reads simple headers that the sidecar guarantees are legitimate.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AuthorizationPolicy&lt;/code&gt; at the destination checks both the mTLS peer identity (is the calling pod's service account allowed?) and the token claims (is the audience correct? is the delegation chain valid?). A token minted for Employee Records cannot be replayed against Device Inventory because the AuthorizationPolicy checks the &lt;code&gt;aud&lt;/code&gt; claim. Both the source principal and the token audience must match for the request to proceed.&lt;/p&gt;
&lt;p&gt;The service calls Cerbos for fine-grained authorization. Can this user, with this role, perform this action on this specific resource? Cerbos returns both an allow/deny decision and a list of visible fields.&lt;/p&gt;
&lt;p&gt;Finally, the database query executes with RLS active. The application calls &lt;code&gt;set_config('app.current_user_id', ...)&lt;/code&gt; on the transaction before querying, and PostgreSQL enforces row visibility at the engine level.&lt;/p&gt;
&lt;p&gt;Header stripping, authentication, token minting, token validation with header projection, authorization policy, Cerbos, and RLS. Seven checkpoints. Each one reduces the blast radius of failures in the others.&lt;/p&gt;
&lt;h3 id=&quot;what-happens-when-you-skip-a-layer&quot;&gt;What Happens When You Skip a Layer&lt;/h3&gt;
&lt;p&gt;Each layer exists because the others have blind spots.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skip header stripping?&lt;/strong&gt; An external attacker sets &lt;code&gt;x-mesh-identity: &amp;lt;forged-token&amp;gt;&lt;/code&gt; in their HTTP request. The token won't pass signature validation at the destination sidecar. But the projected headers (&lt;code&gt;x-ms2-user&lt;/code&gt;, &lt;code&gt;x-ms2-role&lt;/code&gt;) are the real danger. If these arrive from outside without being stripped, and the service reads them directly, the attacker controls the identity. Header stripping ensures no internal trust header survives from outside the mesh, regardless of whether it's a signed token or a raw projected header.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skip signed tokens (use raw headers instead)?&lt;/strong&gt; Any compromised pod can forge headers. Without cryptographic signing, there's no way to distinguish a legitimate identity assertion from one injected by a rogue process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skip authorization policies?&lt;/strong&gt; A valid user with a valid token can call any service and access any resource, regardless of whether they should. Authentication without authorization is an access-all-pass.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skip RLS?&lt;/strong&gt; A bug in your application's authorization logic, a missing check or a broken filter, becomes a data breach. The database has no independent enforcement, so it returns whatever the query asks for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skip Vault (use local keys)?&lt;/strong&gt; If Auth Service is compromised, the attacker has the signing key. They can mint tokens for any user with any role, indefinitely, until you detect the breach and rotate. With Vault Transit, the key never leaves Vault. You revoke Auth Service access and rotate immediately.&lt;/p&gt;
&lt;h3 id=&quot;why-a-service-mesh&quot;&gt;Why a Service Mesh&lt;/h3&gt;
&lt;p&gt;The core idea: your application service has a simple contract. It reads &lt;code&gt;x-ms2-user&lt;/code&gt; and &lt;code&gt;x-ms2-role&lt;/code&gt; from request headers and does its business logic. It doesn't know about mTLS certificates, JWT validation, OIDC flows, or policy engines. The sidecar proxy handles all of that.&lt;/p&gt;
&lt;p&gt;This matters for three reasons.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First, you can add services to the mesh without rewriting their auth logic. The security infrastructure wraps around them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Second, when the security architecture evolves (new signing algorithm, new policy engine, new identity provider) application code doesn't change. Only mesh configuration does.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Third, application developers focus on domain logic while security is configured at the infrastructure layer. These concerns don't bleed into each other.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The complexity is real. You need Kubernetes knowledge, you need to understand Envoy, you need to debug sidecar injection and filter ordering. But the tradeoff is concrete: instead of implementing auth in every single service (and maybe getting it wrong in one of them), you configure it once at the infrastructure layer. The complexity is upfront and centralized instead of distributed across every service where it's invisible until it fails.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next&quot;&gt;What's Next&lt;/h3&gt;
&lt;p&gt;The next post peels back the identity flow, from the gateway &lt;code&gt;EnvoyFilter&lt;/code&gt; that strips headers, through the &lt;code&gt;ExtAuthz&lt;/code&gt; check, to the signed mesh token landing on a downstream service. Real YAML, real Python, real attack scenarios.&lt;/p&gt;
&lt;h3 id=&quot;code-&quot;&gt;Code:&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Kaustav-Sarkar/Istio-Defense-In-Depth&quot;&gt;https://github.com/Kaustav-Sarkar/Istio-Defense-In-Depth&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26024/service-mesh-zero-trust-architecture</guid>
<pubDate>Tue, 01 Sep 2026 22:13:24 +0000</pubDate>
</item>
<item>
<title>Safeguarding Your Systems: An Introduction to the Circuit Breaker Pattern</title>
<link>https://coderlegion.com/25265/safeguarding-your-systems-an-introduction-to-the-circuit-breaker-pattern</link>
<description>&lt;p&gt;The circuit breaker pattern is a modern software design strategy used to detect failures and prevent an application from repeatedly executing an operation that is highly likely to fail. It acts as a protective wrapper around external calls, monitoring for errors and temporarily blocking traffic to a broken dependency once a threshold is crossed. By failing fast, it preserves your system's resources and gives the failing service room to recover.&lt;/p&gt;
&lt;p&gt;Imagine the electrical panel in your home. If a faulty microwave suddenly draws a massive, dangerous surge of electricity, the physical circuit breaker &quot;trips,&quot; immediately shutting off the flow of power to that outlet. It does this to prevent a catastrophic house fire. Once the breaker is tripped, electricity won't flow to that outlet again until you unplug the bad appliance and reset the switch. Without that simple breaker, a single broken device could destroy your entire house.&lt;/p&gt;
&lt;p&gt;In software engineering, we apply this exact logic to distributed systems. Modern applications rely heavily on external third-party services, such as payment processors, SMS gateways, or database clusters. If one of these services crashes or slows to a crawl, a standard application will keep sending requests, waiting endlessly for responses that are never coming. This ties up precious system memory, CPU threads, and network sockets, eventually causing your own application to crash entirely. By implementing a software circuit breaker, engineers can instantly route traffic away from a failing dependency. This allows the application to remain functional, perhaps by showing an &quot;our payment provider is offline&quot; message rather than crashing the entire website.&lt;/p&gt;
&lt;p&gt;This pattern operates using three distinct states: Closed, Open, and Half-Open. In the 'Closed' state, everything is working normally, and all requests pass through. When the error rate exceeds our tolerance, the breaker trips into the 'Open' state, instantly blocking all requests. After a set cool-down period, the breaker enters the 'Half-Open' state, allowing a small test batch of traffic through to see if the service has recovered. If the test succeeds, the breaker closes again; if it fails, it returns to the open state.&lt;/p&gt;
&lt;p&gt;Here is a simple implementation of a Circuit Breaker in JavaScript:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class CircuitBreaker {
  constructor(requestFunction, failureThreshold, cooldownPeriod) {
    this.requestFunction = requestFunction;
    this.failureThreshold = failureThreshold;
    this.cooldownPeriod = cooldownPeriod;
    this.state = &quot;CLOSED&quot;; // CLOSED means normal operation
    this.failureCount = 0;
    this.nextAttemptTime = 0;
  }

  async execute(...args) {
    if (this.state === &quot;OPEN&quot;) {
      if (Date.now() &amp;gt; this.nextAttemptTime) {
        this.state = &quot;HALF-OPEN&quot;; // Try once to see if it's fixed
      } else {
        throw new Error(&quot;Circuit is OPEN. Request blocked to prevent cascade.&quot;);
      }
    }

    try {
      const result = await this.requestFunction(...args);
      this.reset();
      return result;
    } catch (error) {
      this.handleFailure();
      throw error;
    }
  }

  handleFailure() {
    this.failureCount++;
    if (this.failureCount &amp;gt;= this.failureThreshold) {
      this.state = &quot;OPEN&quot;;
      this.nextAttemptTime = Date.now() + this.cooldownPeriod;
    }
  }

  reset() {
    this.failureCount = 0;
    this.state = &quot;CLOSED&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code tracks the status of an external call. If the system fails repeatedly, the breaker enters the &lt;code&gt;OPEN&lt;/code&gt; state, immediately throwing an error for any subsequent attempts without wasting time on network calls. After the &lt;code&gt;cooldownPeriod&lt;/code&gt; expires, it transitions to &lt;code&gt;HALF-OPEN&lt;/code&gt; to test if the service has recovered, resetting itself to &lt;code&gt;CLOSED&lt;/code&gt; on a successful call.&lt;/p&gt;
&lt;p&gt;Embracing the circuit breaker pattern means accepting that failure is an inevitable reality in distributed environments. Instead of hoping that every external API stays online 100% of the time, defensive engineers design systems that can degrade gracefully. Implementing this pattern transforms fragile applications into resilient architectures capable of surviving localized outages without complete system collapse.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;resources-2&quot;&gt;Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Saurav-TB-Pandey/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;react-hook-lab:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect with me on LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/pandeysaurav/&quot;&gt;Saurav Pandey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/08/how-to-prevent-cascading-failures-in.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/saurav_tb_pandey/safeguarding-your-systems-an-introduction-to-the-circuit-breaker-pattern-5dp&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25265/safeguarding-your-systems-an-introduction-to-the-circuit-breaker-pattern</guid>
<pubDate>Fri, 28 Aug 2026 11:35:23 +0000</pubDate>
</item>
<item>
<title>Don't Let One Broken Service Crash Your Whole App: The Circuit Breaker Pattern</title>
<link>https://coderlegion.com/25241/dont-let-one-broken-service-crash-your-whole-app-the-circuit-breaker-pattern</link>
<description>&lt;h3 id=&quot;what-is-the-circuit-breaker-pattern--1&quot;&gt;What is the Circuit Breaker Pattern?&lt;/h3&gt;
&lt;p&gt;The Circuit Breaker Pattern is an architectural design pattern used in software development to detect failures and encapsulate the logic of preventing a failure from cascading throughout a system. It works by wrapping a sensitive network call or database query in a monitoring object that tracks recent failures. When those failures exceed a pre-defined threshold, the breaker trips, automatically blocking subsequent requests to prevent further damage. This prevents a system from repeatedly executing an operation that is almost guaranteed to fail, protecting both the client and the struggling service.&lt;/p&gt;
&lt;h3 id=&quot;a-real-life-analogy-the-household-fuse-box-1&quot;&gt;A Real-Life Analogy: The Household Fuse Box&lt;/h3&gt;
&lt;p&gt;Imagine you are in your kitchen on a weekend morning. You decide to toast some bread, but your toaster has an internal short circuit. When you push down the lever, instead of just warming up, the faulty appliance starts drawing an unsafe, massive surge of electricity. Without a safety device in place, the copper wiring in your kitchen walls would quickly overheat, melt its protective insulation, and potentially start a devastating house fire.&lt;/p&gt;
&lt;p&gt;Fortunately, your home features an electrical circuit breaker panel. The breaker detects this dangerous spike in electrical current and instantly trips, cutting off the flow of electricity to the kitchen outlets. The toaster is dead, but your home is completely safe because the danger was isolated. To fix things, you unplug the toaster, run to the garage, and flip the breaker switch back to its original position to restore power to the remaining kitchen appliances.&lt;/p&gt;
&lt;h3 id=&quot;why-it-matters-in-daily-tech-operations-1&quot;&gt;Why It Matters in Daily Tech Operations&lt;/h3&gt;
&lt;p&gt;In modern software, apps are rarely self-contained. They rely on microservices—smaller, interconnected applications—and external APIs (Application Programming Interfaces) to run. For instance, an e-commerce platform relies on a product catalog service, a payment gateway, and an email service.&lt;/p&gt;
&lt;p&gt;If the payment gateway slows down or crashes during a sale, what happens? Without a circuit breaker, every customer trying to checkout will wait indefinitely. Each waiting user consumes system resources, such as memory and web threads, on your server. As more users click checkout, these resources quickly exhaust. Within minutes, your core checkout service crashes under the pressure, which then causes the inventory service to crash, leading to a complete system outage.&lt;/p&gt;
&lt;p&gt;By implementing the Circuit Breaker Pattern, engineers can safeguard their systems. Once the circuit breaker detects that the payment gateway is failing, it immediately trips. New checkout requests instantly receive a friendly message like, &quot;Our payment gateway is busy, please try again in a moment.&quot; This spares your servers from waiting on a dead service, allowing the rest of your website to remain online and functional.&lt;/p&gt;
&lt;h3 id=&quot;the-concept-in-action-1&quot;&gt;The Concept in Action&lt;/h3&gt;
&lt;p&gt;Here is a simple, lightweight implementation of a circuit breaker in JavaScript to show how this logic looks under the hood:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class SimpleCircuitBreaker {
  constructor(requestFunction, failureThreshold = 3, cooldownMs = 10000) {
    this.requestFunction = requestFunction;
    this.failureThreshold = failureThreshold;
    this.cooldownMs = cooldownMs;
    this.state = &quot;CLOSED&quot;;
    this.failures = 0;
    this.nextAttemptTime = 0;
  }

  async execute() {
    if (this.state === &quot;OPEN&quot;) {
      if (Date.now() &amp;gt; this.nextAttemptTime) {
        this.state = &quot;HALF-OPEN&quot;;
      } else {
        throw new Error(&quot;Circuit is OPEN. Request blocked for safety.&quot;);
      }
    }

    try {
      const result = await this.requestFunction();
      this.reset();
      return result;
    } catch (error) {
      this.handleFailure();
      throw error;
    }
  }

  reset() {
    this.state = &quot;CLOSED&quot;;
    this.failures = 0;
  }

  handleFailure() {
    this.failures++;
    if (this.failures &amp;gt;= this.failureThreshold) {
      this.state = &quot;OPEN&quot;;
      this.nextAttemptTime = Date.now() + this.cooldownMs;
      console.log(&quot;Circuit breaker tripped! Blocking future requests.&quot;);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-takeaway-3&quot;&gt;The Takeaway&lt;/h3&gt;
&lt;p&gt;Building highly reliable modern software systems is not about pretending that failures will never occur; it is about managing those failures gracefully when they inevitably do. The Circuit Breaker Pattern shifts our architectural strategy from fragile, hopeful connections to defensive, self-healing resilience. By failing fast, your application protects its precious memory and computing power, preventing localized outages from snowballing into catastrophic system-wide downtime.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;resources-3&quot;&gt;Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Saurav-TB-Pandey/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;react-hook-lab:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect with me on LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/pandeysaurav/&quot;&gt;Saurav Pandey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/08/how-to-build-resilient-apps-with.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/saurav_tb_pandey/dont-let-one-broken-service-crash-your-whole-app-the-circuit-breaker-pattern-12j7&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25241/dont-let-one-broken-service-crash-your-whole-app-the-circuit-breaker-pattern</guid>
<pubDate>Wed, 26 Aug 2026 01:16:39 +0000</pubDate>
</item>
<item>
<title>Continuous Delivery Office Hours Ep.4: Mono, micro, mesco</title>
<link>https://coderlegion.com/23582/continuous-delivery-office-hours-ep-4-mono-micro-mesco</link>
<description>&lt;p&gt;After tackling the question of &lt;a rel=&quot;nofollow&quot; href=&quot;/blog/continuous-delivery-office-hours-e3&quot;&gt;which branching strategy is best&lt;/a&gt;, we turn our focus to architecture in this Continuous Delivery Office Hours episode. In particular, we discuss how big your building blocks should be and whether you should opt for a majestic monolith, granular microservices, or something in between.&lt;/p&gt;
&lt;p&gt;Traditional monoliths are often described as a &lt;em&gt;big ball of mud&lt;/em&gt; or &lt;em&gt;spaghetti code&lt;/em&gt; due to the complicated mess of dependencies. When all the code is co-located, it's common to find a tangled web of paths, with interfaces and facades skipped in favor of direct access to low-level functions and classes. This kind of code is hard to maintain.&lt;/p&gt;
&lt;p&gt;Majestic monoliths are well-structured and loosely coupled, with carefully designed dependencies that are sometimes enforced by architectural tools. By making things modular, you get the benefits often associated with microservices, but without the difficulty of finding the code or the runtime complexity of distributed systems.&lt;/p&gt;
&lt;p&gt;Microservices go a step further by physically separating parts of the system into independently deployable units. In addition to the architectural benefits, you can get faster build and test run times because you only build and test each service when it changes, which involves much less code. The trade-off is that your codebase becomes more complex, and you have to deal with versioning and issue pinpointing across all the deployed services.&lt;/p&gt;
&lt;p&gt;So, how do you choose between these approaches?&lt;/p&gt;
&lt;h3 id=&quot;watch-the-episode&quot;&gt;Watch the episode&lt;/h3&gt;
&lt;p&gt;You can watch the episode below, or read on to find some of the key discussion points.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=CuFg-Cjib5Q&quot;&gt;Watch Continuous Delivery Office Hours Ep.4&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;architectural-decision-factors&quot;&gt;Architectural decision factors&lt;/h3&gt;
&lt;p&gt;Many factors influence the decision between monoliths, microservices, and hybrid approaches. One of the most crucial is how your teams are structured and how much autonomy they need. There are different ways to design &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/devops/culture/team-structures/&quot;&gt;team structures&lt;/a&gt;, like Team Topologies, that make your teams and architecture part of the same conversation.&lt;/p&gt;
&lt;p&gt;This is consistent with the &lt;em&gt;inverse Conway maneuver&lt;/em&gt;, in which you structure your teams to match your target architecture. This works because Conway's Law states that organizations design systems that mirror their communication structures. Working at the team level is more effective than creating an architectural diagram, as the structure will naturally yield the design you want.&lt;/p&gt;
&lt;p&gt;Having a mismatch between the number of teams and the number of components can add complexity, either because too many teams are working on a single module, or you have created so many modules that there aren't enough teams to own all the maintenance tasks associated with them. A sensible default is to match the number of teams to the number of components, ensuring sufficient autonomy for each team to work independently without overburdening them with excessive complexity.&lt;/p&gt;
&lt;p&gt;The matching of service and team design sits between monolith and microservices, and might be described as &quot;mescoservices&quot; to highlight the balance they strike between the macro level of monoliths and the fine-grained level of microservices.&lt;/p&gt;
&lt;p&gt;Another factor that can influence your decision is your deployment model. A managed software-as-a-service offering can use microservices without imposing complexity on its customers. In contrast, a self-hosted software offering may benefit from easier installation, troubleshooting, and upgrades if it uses a monolithic architecture.&lt;/p&gt;
&lt;h3 id=&quot;common-microservice-failure-modes&quot;&gt;Common microservice failure modes&lt;/h3&gt;
&lt;p&gt;There are several common failure modes to watch out for when moving to a microservice architecture.&lt;/p&gt;
&lt;p&gt;Distributed monoliths look like microservices, except they have dependencies that mean you have to deploy services in a specific order. If you can't deploy independently, you don't have microservices. Similarly, you often find microservices at the application layer, but all sharing the same database. This prevents independent scaling of high-load services and can cause services to access data directly rather than through the appropriate service.&lt;/p&gt;
&lt;p&gt;When you deploy large numbers of microservices, you may need to introduce rules about which services can communicate. Allowing any service to call any other service means managing a complex web of dependencies, and it starts to look a lot like the proverbial &lt;em&gt;big ball of mud&lt;/em&gt;. This is often the result of splitting a monolith into microservices, as the old design tends to persist.&lt;/p&gt;
&lt;p&gt;Microservices should bring a high level of team independence, allowing teams to make decisions about their own services, persistence, and deployment schedules.&lt;/p&gt;
&lt;h3 id=&quot;pulling-microservices-together&quot;&gt;Pulling microservices together&lt;/h3&gt;
&lt;p&gt;To make microservices successful, you need to establish data ownership and decide what level of data duplication is acceptable to support the business goals. For example, there's a temptation to store a single address for a customer, but it may be a better idea to store the address immutably against events, like orders. Even if the customer moves house, the address for a past order remains the same.&lt;/p&gt;
&lt;p&gt;Domain-driven design provides a method and a language for making decisions across service boundaries. A principal engineer or architect would usually set standards for service boundaries, contracts, and versioning to help keep services loosely coupled and independently deployable.&lt;/p&gt;
&lt;h3 id=&quot;fitness-test&quot;&gt;Fitness test&lt;/h3&gt;
&lt;p&gt;The best test of your service-based architecture is independent deployability. If each team can deploy its services without cross-team coordination, then your architecture is working. For majestic monoliths, the test is similar; teams should be able to work independently on modules they own. While deployments will be coupled through a single build and deployment pipeline, they should be able to make and commit changes independently.&lt;/p&gt;
&lt;p&gt;Happy deployments!&lt;/p&gt;
&lt;p&gt;Continuous Delivery Office Hours is a series of conversations about software delivery, with Tony Kelly, Bob Walker, and Steve Fenton.&lt;/p&gt;
&lt;p&gt;You can find more episodes on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/playlist?list=PLAGskdGvlaw3CrxkUOAMmiy928lr5D4oh&quot;&gt;YouTube&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://podcasts.apple.com/us/podcast/continuous-delivery-office-hours/id1872101651&quot;&gt;Apple Podcasts&lt;/a&gt;, and &lt;a rel=&quot;nofollow&quot; href=&quot;https://pca.st/hwjaox59&quot;&gt;Pocket Casts&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23582/continuous-delivery-office-hours-ep-4-mono-micro-mesco</guid>
<pubDate>Mon, 27 Jul 2026 12:09:46 +0000</pubDate>
</item>
<item>
<title>Mono, Micro, Mesco</title>
<link>https://coderlegion.com/21258/mono-micro-mesco</link>
<description>&lt;p&gt;Tony Kelly hosts our latest episode of Continuous Delivery Office Hours, with Bob Walker and Steve Fenton.&lt;/p&gt;
&lt;p&gt;Your system's architecture can support team autonomy, independent deployability, and independent evolution of components, or it can be a sticky mess. This episode looks at whether choosing a monolith or microservices fundamentally changes the crucial properties of your architecture.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The many factors influencing architecture&lt;/li&gt;
&lt;li&gt;How team design is part of architecture&lt;/li&gt;
&lt;li&gt;How to choose between monoliths and microservices&lt;/li&gt;
&lt;li&gt;The true measure of successful architecture&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/21258/mono-micro-mesco</guid>
<pubDate>Wed, 15 Jul 2026 09:00:00 +0000</pubDate>
</item>
<item>
<title>Designing Healthcare Interoperability with HL7 FHIR: Lessons from Venezuela</title>
<link>https://coderlegion.com/19410/designing-healthcare-interoperability-with-hl7-fhir-lessons-from-venezuela</link>
<description>&lt;h3 id=&quot;how-to-move-from-isolated-hospital-systems-to-event-driven-fhir-based-architectures-in-a-context-with-poor-connectivity-and-legacy-vendors-&quot;&gt;&lt;strong&gt;How to move from isolated hospital systems to event‑driven, FHIR‑based architectures in a context with poor connectivity and legacy vendors.&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In many private clinics in Venezuela, “digital transformation” has meant installing a Hospital Information System (HIS) and maybe an Electronic Health Record (EHR). The problem is that most of these systems do not expose standard APIs, or do it in a very limited way.&lt;/p&gt;
&lt;p&gt;As an engineer, the real challenge is not “going paperless”, but making systems talk to each other: getting lab, pharmacy, admissions, telemedicine and billing to exchange data without adding more technical debt.&lt;/p&gt;
&lt;p&gt;In this post I’ll share a practical architectural approach, from a developer’s perspective, to move a typical clinic environment from ad‑hoc integrations to an HL7 FHIR + event‑driven architecture.&lt;/p&gt;
&lt;p&gt;The starting point: interoperability level 1–2&lt;br&gt;
In practice, most clinics I work with operate like this:&lt;/p&gt;
&lt;p&gt;Manual CSV or PDF exports.&lt;/p&gt;
&lt;p&gt;Occasional point‑to‑point HL7 v2 interfaces (if you’re lucky).&lt;/p&gt;
&lt;p&gt;No formal API documentation.&lt;/p&gt;
&lt;p&gt;Human “copy‑paste” processes between systems.&lt;/p&gt;
&lt;p&gt;In HIMSS terms, that’s level 1–2 interoperability. A realistic technical goal is to bring critical systems up to level 3 (semantic), where data means the same thing everywhere.&lt;/p&gt;
&lt;p&gt;Why FHIR fits this environment&lt;br&gt;
FHIR brings several properties that are extremely valuable in this context:&lt;/p&gt;
&lt;p&gt;API‑first. Everything revolves around HTTP resources.&lt;/p&gt;
&lt;p&gt;Well‑known structures. JSON with clear fields for Patient, Observation, Encounter, Medication, etc.&lt;/p&gt;
&lt;p&gt;Mature implementations. HAPI FHIR in Java, for example, lets you spin up a server quickly.&lt;/p&gt;
&lt;p&gt;A minimal viable pattern looks like this:&lt;/p&gt;
&lt;p&gt;Deploy a FHIR server (e.g., HAPI FHIR) as the central point.&lt;/p&gt;
&lt;p&gt;Expose a small, focused subset of resources for the first use case: Patient, Encounter, Observation, Medication, DiagnosticReport.&lt;/p&gt;
&lt;p&gt;Build one adapter per legacy system that:&lt;/p&gt;
&lt;p&gt;Consumes events or exports from the source system.&lt;/p&gt;
&lt;p&gt;Transforms them into the corresponding FHIR resource.&lt;/p&gt;
&lt;p&gt;Publishes them to the FHIR server via its REST API.&lt;/p&gt;
&lt;p&gt;Integration layer: avoiding point‑to‑point hell&lt;br&gt;
Without an integration layer, every integration is a fixed pair of systems. With ten systems, you have forty‑five possible pairs.&lt;/p&gt;
&lt;p&gt;Recommended architecture:&lt;/p&gt;
&lt;p&gt;An event bus (Apache Kafka for high‑volume scenarios, RabbitMQ for more modest loads).&lt;/p&gt;
&lt;p&gt;Microservices (Java + Spring Boot, for example) responsible for:&lt;/p&gt;
&lt;p&gt;Subscribing to domain‑specific topics (lab, pharmacy, admissions, etc.).&lt;/p&gt;
&lt;p&gt;Transforming messages to the FHIR model.&lt;/p&gt;
&lt;p&gt;Publishing to the FHIR server.&lt;/p&gt;
&lt;p&gt;In parallel, an integration engine like Mirth Connect can simplify HL7 v2 → FHIR handling.&lt;/p&gt;
&lt;p&gt;This shifts the problem from “every system talks to every other system” to “every system talks to the bus”, and “the bus talks FHIR to the outside world”.&lt;/p&gt;
&lt;p&gt;Dealing with unreliable connectivity&lt;br&gt;
In Venezuela, you simply cannot assume stable connectivity, not even within the same city. From a software architecture standpoint, this means:&lt;/p&gt;
&lt;p&gt;Implementing the Outbox Pattern in source systems.&lt;/p&gt;
&lt;p&gt;Using local queues to buffer events when the connection is down, and flush them when it comes back.&lt;/p&gt;
&lt;p&gt;Designing idempotent consumers on the event bus side to avoid duplicates and inconsistencies.&lt;/p&gt;
&lt;p&gt;From this perspective, the FHIR server becomes the consolidated source of truth, but local systems must still be able to operate with a subset of data while offline.&lt;/p&gt;
&lt;p&gt;Security by design, not as an afterthought&lt;br&gt;
Two concrete recommendations:&lt;/p&gt;
&lt;p&gt;For client applications (web, mobile, clinical front‑end): SMART on FHIR on top of OAuth 2.0, with fine‑grained scopes per resource.&lt;/p&gt;
&lt;p&gt;For server‑to‑server integrations: OAuth 2.0 with trusted clients, managed certificates and TLS 1.3 everywhere.&lt;/p&gt;
&lt;p&gt;Trying to “bolt on” security at the end is a terrible idea in a domain where data is, by definition, highly sensitive.&lt;/p&gt;
&lt;p&gt;Conclusion and next steps&lt;br&gt;
If you’re in a similar environment (private clinic in LATAM with multiple legacy systems) and want to start working with FHIR, my suggestion is:&lt;/p&gt;
&lt;p&gt;Pick one small but critical use case (for instance, integrating lab results into the EHR).&lt;/p&gt;
&lt;p&gt;Stand up a FHIR server with a minimal resource model.&lt;/p&gt;
&lt;p&gt;Build a single adapter for one source system.&lt;/p&gt;
&lt;p&gt;Add the event bus and security from day one, even if you only have a couple of systems.&lt;/p&gt;
&lt;p&gt;From there, adding new systems becomes a repeatable pattern, not a heroic project every single time.&lt;/p&gt;
&lt;p&gt;Call to action for dev.to&lt;br&gt;
If you’re working on healthcare interoperability in LATAM and want to compare architectures or trade war stories, I’d be happy to connect. You can find more details and case studies at &lt;a rel=&quot;nofollow&quot; href=&quot;https://codebymelendez.com/&quot;&gt;codebymelendez.com&lt;/a&gt; or reach out to me on LinkedIn.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19410/designing-healthcare-interoperability-with-hl7-fhir-lessons-from-venezuela</guid>
<pubDate>Mon, 01 Jun 2026 10:23:52 +0000</pubDate>
</item>
<item>
<title>Building a Real-Time Network Anomaly Detector with PyTorch and Go</title>
<link>https://coderlegion.com/18965/building-a-real-time-network-anomaly-detector-with-pytorch-and-go</link>
<description>&lt;hr&gt;
&lt;p&gt;description: &quot;4-service microservices architecture for live network intrusion detection: hybrid ML model, sub-second alerts, and an 11-page React operator dashboard.&quot;&lt;br&gt;
tags: pytorch, go, machinelearning, microservices&lt;br&gt;
series: engineering-deep-dives&lt;br&gt;
cover_image:&lt;/p&gt;
&lt;h3 id=&quot;canonical-url-https-satyasaikilaru-vercel-app-blog-network-anomaly-detector&quot;&gt;canonical_url: &lt;a rel=&quot;nofollow&quot; href=&quot;https://satyasaikilaru.vercel.app/blog/network-anomaly-detector&quot;&gt;https://satyasaikilaru.vercel.app/blog/network-anomaly-detector&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — I built a full-stack intrusion detection platform: a hybrid ML model (Focal Loss classifier + autoencoder for zero-days), a 4-service Go/Python backend, and an 11-page React dashboard. R2L recall doubled (14% → 29%), zero-day detection works via reconstruction error, and the whole pipeline runs in &amp;lt;2ms per prediction.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-no-one-talks-about&quot;&gt;The Problem No One Talks About&lt;/h3&gt;
&lt;p&gt;Every network security tutorial shows you how to train a classifier on NSL-KDD and call it done. But classifiers have a fatal blind spot: &lt;strong&gt;they can only detect attacks they've trained on.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The NSL-KDD test set alone contains 17 attack subtypes that don't appear in training. In production, this ratio is infinitely worse — every new CVE, every new botnet, every novel C2 protocol is invisible to a classifier until you retrain.&lt;/p&gt;
&lt;p&gt;I wanted to build something that catches &lt;em&gt;both&lt;/em&gt;: known attacks with high precision &lt;strong&gt;and&lt;/strong&gt; novel threats it's never seen before.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;architecture-at-a-glance&quot;&gt;Architecture at a Glance&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;┌─────────────────────────┐
│   React Dashboard :5173 │  11 pages, ECharts, AG Grid, Firebase Auth
│   Apollo Client (GQL+WS)│
└───────────┬─────────────┘
            │
┌───────────▼─────────────┐
│  GraphQL Gateway :4000  │  gqlgen, Firebase Admin SDK, SQLite
│  Auth · Persistence     │
└───────────┬─────────────┘
            │ HTTP proxy
┌───────────▼─────────────┐
│  Go API Backend :8080   │  REST, WebSocket broadcast, Simulation
│  Packet capture control │
└───────────┬─────────────┘
            │ HTTP /api/predict
┌───────────▼─────────────┐
│  FastAPI ML Server :8000│  PyTorch → ONNX inference, &amp;lt;2ms/sample
└─────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why 4 services?&lt;/strong&gt; The ML server needs high memory and bursty CPU. The Go API needs low memory and high concurrency. Scaling them as one binary wastes money. Each service deploys and scales independently.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-hybrid-ml-model&quot;&gt;The Hybrid ML Model&lt;/h3&gt;
&lt;h4 id=&quot;problem-1-rare-attacks-are-invisible-to-standard-loss-functions&quot;&gt;Problem 1: Rare attacks are invisible to standard loss functions&lt;/h4&gt;
&lt;p&gt;NSL-KDD class distribution:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Class &lt;/th&gt;&lt;th&gt;Training % &lt;/th&gt;&lt;th&gt;What it is&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Normal &lt;/td&gt;&lt;td&gt;53% &lt;/td&gt;&lt;td&gt;Legitimate traffic&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DoS &lt;/td&gt;&lt;td&gt;36% &lt;/td&gt;&lt;td&gt;Denial of service&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Probe &lt;/td&gt;&lt;td&gt;9.4% &lt;/td&gt;&lt;td&gt;Port scanning, reconnaissance&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;R2L &lt;/td&gt;&lt;td&gt;0.8% &lt;/td&gt;&lt;td&gt;Remote-to-Local exploits&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;U2R &lt;/td&gt;&lt;td&gt;0.04% &lt;/td&gt;&lt;td&gt;User-to-Root privilege escalation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Standard CrossEntropy loss treats all samples equally — so the model essentially ignores R2L and U2R. Why learn to detect 0.04% of the data when you can boost accuracy by getting the 53% right?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix: Focal Loss.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;FL(p_t) = -α_t (1 - p_t)^γ log(p_t)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Focal Loss down-weights easy examples (the Normal traffic the model already handles) and forces it to focus on hard, rare samples. The γ parameter controls how aggressively.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;CrossEntropy (V1) &lt;/th&gt;&lt;th&gt;Focal Loss (V2) &lt;/th&gt;&lt;th&gt;Change&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Probe Recall &lt;/td&gt;&lt;td&gt;68% &lt;/td&gt;&lt;td&gt;88% &lt;/td&gt;&lt;td&gt;+20%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;R2L Recall &lt;/td&gt;&lt;td&gt;14% &lt;/td&gt;&lt;td&gt;29% &lt;/td&gt;&lt;td&gt;&lt;strong&gt;+15% (2x)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Macro F1 &lt;/td&gt;&lt;td&gt;0.558 &lt;/td&gt;&lt;td&gt;0.589 &lt;/td&gt;&lt;td&gt;+5.5%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;problem-2-zero-days-are-invisible-to-any-classifier&quot;&gt;Problem 2: Zero-days are invisible to any classifier&lt;/h4&gt;
&lt;p&gt;A classifier is a lookup table — &quot;I've seen this pattern before, it's DoS.&quot; For the 17 novel attack subtypes in the test set (and the infinite unknown attacks in the wild), we need something fundamentally different.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix: Autoencoder trained only on Normal traffic.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The autoencoder learns to compress and reconstruct &quot;what normal looks like.&quot; At inference:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low reconstruction error → looks normal → pass&lt;/li&gt;
&lt;li&gt;High reconstruction error → looks like &lt;em&gt;nothing I've ever seen&lt;/em&gt; → anomaly flag&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is zero-day detection by definition: it catches anything that deviates from normal, regardless of whether it matches any known signature.&lt;/p&gt;
&lt;h4 id=&quot;the-3-vote-consensus&quot;&gt;The 3-Vote Consensus&lt;/h4&gt;
&lt;p&gt;Two detectors means disagreements. Policy:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Classifier &lt;/th&gt;&lt;th&gt;Autoencoder &lt;/th&gt;&lt;th&gt;Action&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Attack &lt;/td&gt;&lt;td&gt;High error &lt;/td&gt;&lt;td&gt;&lt;strong&gt;High confidence&lt;/strong&gt; — auto-block above threshold&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Normal &lt;/td&gt;&lt;td&gt;High error &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Possible zero-day&lt;/strong&gt; — escalate to operator&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Attack &lt;/td&gt;&lt;td&gt;Low error &lt;/td&gt;&lt;td&gt;Known pattern — treat at face value&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Normal &lt;/td&gt;&lt;td&gt;Low error &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Green&lt;/strong&gt; — pass&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The second row is the gold: a connection that &lt;em&gt;looks normal per-feature&lt;/em&gt; but whose overall shape is anomalous. That's where zero-days hide.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-dashboard-11-pages&quot;&gt;The Dashboard — 11 Pages&lt;/h3&gt;
&lt;p&gt;Not just a prediction endpoint. Operators need context:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Page &lt;/th&gt;&lt;th&gt;Why it exists&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;World Map&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Animated attack routes with GeoIP — &quot;where is this coming from?&quot;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Traffic Feed&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;AG Grid, bulk select, CSV export — &quot;show me everything&quot;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Incidents&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;AI threat summary + timeline by class — &quot;what's happening now?&quot;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Analytics&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Time-series, attack distribution — &quot;what's the trend?&quot;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Network Graph&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Force-directed IP topology — &quot;who's talking to whom?&quot;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Model Compare&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Radar chart, confusion matrix — &quot;how good are we?&quot;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SOAR&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Auto-block on confidence thresholds — &quot;respond automatically&quot;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Settings&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Profile, theme, webhooks, Slack — &quot;configure everything&quot;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;System Status&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Service health, latency sparklines — &quot;is the platform healthy?&quot;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Firebase Auth handles Google Sign-In + Email/Password. First user = admin in SQLite; subsequent = viewers. RBAC enforced at the gateway, not just the UI.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;performance&quot;&gt;Performance&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Class &lt;/th&gt;&lt;th&gt;Precision &lt;/th&gt;&lt;th&gt;Recall &lt;/th&gt;&lt;th&gt;F1&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Normal &lt;/td&gt;&lt;td&gt;91.0% &lt;/td&gt;&lt;td&gt;88.9% &lt;/td&gt;&lt;td&gt;90.0%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DoS &lt;/td&gt;&lt;td&gt;95.8% &lt;/td&gt;&lt;td&gt;85.6% &lt;/td&gt;&lt;td&gt;90.4%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Probe &lt;/td&gt;&lt;td&gt;60.4% &lt;/td&gt;&lt;td&gt;88.3% &lt;/td&gt;&lt;td&gt;71.7%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;R2L &lt;/td&gt;&lt;td&gt;53.9% &lt;/td&gt;&lt;td&gt;29.4% &lt;/td&gt;&lt;td&gt;38.0%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;U2R &lt;/td&gt;&lt;td&gt;2.2% &lt;/td&gt;&lt;td&gt;41.8% &lt;/td&gt;&lt;td&gt;4.2%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Overall: 80.0% accuracy · 0.589 Macro F1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;R2L/U2R are still the weakest — genuinely hard with &amp;lt;1% of training data. The autoencoder compensates by catching them as distribution anomalies even when the classifier misses.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-d-do-differently&quot;&gt;What I'd Do Differently&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add a χ² frequency-distribution detector&lt;/strong&gt; — the same math that cracks a Caesar cipher (compare observed letter frequencies to English) applied to network feature distributions. Catches slow horizontal scans that per-packet models miss.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Per-host behavioral baselines&lt;/strong&gt; — a global Normal model doesn't notice &quot;this IP was quiet for 6 months and just started scanning port 445.&quot; A per-host baseline does.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Adversarial training&lt;/strong&gt; — FGSM/PGD perturbations during training to harden against evasion. If someone discovers your model, they'll craft packets to slip past it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stack&quot;&gt;Stack&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Go&lt;/code&gt; · &lt;code&gt;Python&lt;/code&gt; · &lt;code&gt;PyTorch&lt;/code&gt; · &lt;code&gt;ONNX&lt;/code&gt; · &lt;code&gt;FastAPI&lt;/code&gt; · &lt;code&gt;React 19&lt;/code&gt; · &lt;code&gt;Vite&lt;/code&gt; · &lt;code&gt;Tailwind CSS&lt;/code&gt; · &lt;code&gt;ECharts&lt;/code&gt; · &lt;code&gt;AG Grid&lt;/code&gt; · &lt;code&gt;Apollo Client&lt;/code&gt; · &lt;code&gt;GraphQL (gqlgen)&lt;/code&gt; · &lt;code&gt;Firebase Auth&lt;/code&gt; · &lt;code&gt;SQLite&lt;/code&gt; · &lt;code&gt;scapy&lt;/code&gt; · &lt;code&gt;GeoIP2&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://satyasaikilaru.vercel.app/&quot;&gt;Portfolio&lt;/a&gt;&lt;/strong&gt; · &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/SatyaKilaru&quot;&gt;GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you're building something similar or want to discuss the architecture — connect with me on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/satya-sai-kilaru-09634a1b7/&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18965/building-a-real-time-network-anomaly-detector-with-pytorch-and-go</guid>
<pubDate>Tue, 26 May 2026 07:41:30 +0000</pubDate>
</item>
<item>
<title>The Ecosystem Conductor: Orchestrating Market Orders with Kafka, RabbitMQ and Spring Boot</title>
<link>https://coderlegion.com/17660/the-ecosystem-conductor-orchestrating-market-orders-with-kafka-rabbitmq-and-spring-boot</link>
<description>&lt;p&gt;Hey folks!&lt;/p&gt;
&lt;p&gt;We've reached one of the most important posts in the &lt;strong&gt;My Broker B3&lt;/strong&gt; series. Up until now we built each piece of the ecosystem separately — the price synchronizer, the matching engine, the financial custody, the asset catalog. Now it's time to build the service that connects all of it: the &lt;strong&gt;Broker Order API&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is the conductor of the ecosystem. When a user wants to buy or sell a stock, this is where everything begins — and where the results come back to.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-is-the-order-api-&quot;&gt;What is the Order API?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;trading-broker-order&lt;/code&gt; is the entry point for the broker's transactional flow. It has a clear responsibility: &lt;strong&gt;orchestrate the complete lifecycle of an order&lt;/strong&gt;, from the user's intent to the final execution confirmation.&lt;/p&gt;
&lt;p&gt;To do this, it communicates with four other ecosystem services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Asset API&lt;/strong&gt; (REST/Feign) — validates whether the ticker exists and is active&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wallet API&lt;/strong&gt; (REST/Feign) — validates whether the user has sufficient balance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B3 Matching Engine&lt;/strong&gt; (RabbitMQ) — sends the order for execution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Broker Wallet&lt;/strong&gt; (Kafka) — notifies the ecosystem about each status change&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-the-complete-order-flow&quot;&gt;️ The Complete Order Flow&lt;/h2&gt;
&lt;p&gt;Before diving into the code, here's the end-to-end flow:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[User]
    │
    │  POST /api/v1/orders
    │  { userId, ticker, quantity, price, side: &quot;BUY&quot; }
    ▼
[trading-broker-order]
    │
    ├─ 1. Validate ticker  → GET trading-asset/assets/{ticker} (Feign/REST)
    │
    ├─ 2. Validate balance → GET trading-wallet/summary (Feign/REST)
    │
    ├─ 3. Persist order → MySQL (status: PENDING)
    │
    ├─ 4. Send to B3 → RabbitMQ (mq-broker-to-b3)
    │
    ├─ 5. Notify ecosystem → Kafka (order-events-v1, status: PENDING)
    │
    └─ Returns created order → HTTP 201

         ─ ─ ─ (B3 processes the order) ─ ─ ─

[b3-matching-engine]
    │
    │  RabbitMQ (mq-b3-to-broker)
    │  { orderId, status: FILLED/REJECTED, executedPrice }
    ▼
[trading-broker-order]
    │
    ├─ 6. Update status → MySQL (FILLED or REJECTED)
    │
    └─ 7. Notify ecosystem → Kafka (order-events-v1, final status)
              │
              ├─ trading-broker-wallet consumes → blocks/settles/refunds balance
              └─ other future consumers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two distinct communication channels, two Kafka publish moments, and the wallet reacting to each state change. Let's see how each part was implemented.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-tech-stack&quot;&gt;️ Tech Stack&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Usage&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Java 21&lt;/strong&gt; + &lt;strong&gt;Spring Boot 3.3.5&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Service core&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;MySQL&lt;/strong&gt; + &lt;strong&gt;Flyway&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Order persistence&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Kafka&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Lifecycle event publishing&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring AMQP&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Integration with Matching Engine via RabbitMQ&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Cloud OpenFeign&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;REST calls to Asset API and Wallet API&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SpringDoc OpenAPI&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Swagger UI documentation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-implementation-pillars&quot;&gt;️ Implementation Pillars&lt;/h2&gt;
&lt;h3 id=&quot;1-the-domain-model-order&quot;&gt;1. The Domain Model — Order&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;Order&lt;/code&gt; entity represents the state of an order at any point in its lifecycle:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity
@Table(name = &quot;orders&quot;)
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String userId;
    private String ticker;

    @Column(nullable = false, precision = 19, scale = 4)
    private BigDecimal quantity;

    @Column(nullable = false, precision = 19, scale = 4)
    private BigDecimal price;

    // Null while PENDING — only set after B3 feedback
    @Column(precision = 19, scale = 4)
    private BigDecimal executedPrice;

    @Enumerated(EnumType.STRING)
    private OrderSide side;   // BUY | SELL

    @Enumerated(EnumType.STRING)
    private OrderStatus status; // PENDING | FILLED | REJECTED | CANCELLED

    @CreationTimestamp
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An important design detail: &lt;code&gt;executedPrice&lt;/code&gt; is intentionally nullable. When the order is created, we don't yet know the execution price — that only comes as a response from the Matching Engine. Forcing &lt;code&gt;nullable = false&lt;/code&gt; here would be a domain modeling error.&lt;/p&gt;
&lt;p&gt;The entity also carries the &lt;code&gt;mapToEvent()&lt;/code&gt; method that serializes the current state into a Kafka event:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public OrderEventDTO mapToEvent() {
    return OrderEventDTO.builder()
            .orderId(this.getId())
            .userId(this.getUserId())
            .ticker(this.getTicker())
            .quantity(this.getQuantity())
            .price(this.getPrice())
            .executedPrice(this.getExecutedPrice())
            .side(this.getSide().name())
            .status(this.getStatus().name())
            .eventTimestamp(LocalDateTime.now())
            .build();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-ticker-validation-asset-feign-client&quot;&gt;2. Ticker Validation — Asset Feign Client&lt;/h3&gt;
&lt;p&gt;Before validating balance or persisting anything, we need to ensure the requested ticker actually exists and is available for trading. A user submitting an order for &lt;code&gt;XPTO999&lt;/code&gt; should get a clear error immediately — not a silent rejection from B3 two hops later.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@FeignClient(name = &quot;trading-broker-asset&quot;, url = &quot;${app.services.asset-url}&quot;)
public interface AssetClient {

    @GetMapping(&quot;/api/v1/assets/{ticker}&quot;)
    AssetDTO getByTicker(@PathVariable(&quot;ticker&quot;) String ticker);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The validation in &lt;code&gt;OrderService&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private void validateTicker(String ticker) {
    try {
        var asset = assetClient.getByTicker(ticker.toUpperCase());
        if (!&quot;ACTIVE&quot;.equalsIgnoreCase(asset.status())) {
            throw new RuntimeException(&quot;Asset is not available for trading: &quot; + ticker);
        }
        log.info(&quot;Ticker {} validated — status: {}&quot;, ticker, asset.status());
    } catch (FeignException.NotFound e) {
        throw new RuntimeException(&quot;Asset not found or inactive: &quot; + ticker);
    } catch (FeignException e) {
        log.error(&quot;Asset service unavailable during ticker validation: {}&quot;, e.getMessage());
        throw new RuntimeException(&quot;Asset service unavailable. Please try again later.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why catch &lt;code&gt;FeignException&lt;/code&gt; separately from &lt;code&gt;FeignException.NotFound&lt;/code&gt;?&lt;/strong&gt; A 404 from the asset service means the ticker doesn't exist — a business error that should fail fast. Any other &lt;code&gt;FeignException&lt;/code&gt; (connection refused, timeout, 5xx) means the asset service is unavailable — a different kind of failure that warrants a different message to the user.&lt;/p&gt;
&lt;h3 id=&quot;3-balance-validation-wallet-feign-client&quot;&gt;3. Balance Validation — Wallet Feign Client&lt;/h3&gt;
&lt;p&gt;For buy orders (&lt;code&gt;BUY&lt;/code&gt;), we need to ensure the user has available balance. We use Spring Cloud OpenFeign to query the Wallet API declaratively:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@FeignClient(name = &quot;trading-broker-wallet&quot;, url = &quot;${app.services.wallet-url}&quot;)
public interface WalletClient {

    @GetMapping(&quot;/api/v1/wallet/{userId}/summary&quot;)
    WalletSummaryDTO getWalletSummary(@PathVariable String userId);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The validation happens after ticker validation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private void validateBalance(OrderRequestDTO request) {
    BigDecimal totalOrderValue = request.getPrice().multiply(request.getQuantity());
    var walletSummary = walletClient.getWalletSummary(request.getUserId());

    if (walletSummary.getAvailableBalance().compareTo(totalOrderValue) &amp;lt; 0) {
        throw new RuntimeException(&quot;Insufficient balance. Available: &quot;
                + walletSummary.getAvailableBalance()
                + &quot; | Required: &quot; + totalOrderValue);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why Feign and not RestTemplate?&lt;/strong&gt; OpenFeign eliminates HTTP boilerplate — the interface is sufficient. The URL is injected via &lt;code&gt;application.yaml&lt;/code&gt;, making environment changes straightforward without recompiling.&lt;/p&gt;
&lt;h3 id=&quot;4-the-creation-flow-orderservice&quot;&gt;4. The Creation Flow — OrderService&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;placeOrder()&lt;/code&gt; is the heart of the service. It orchestrates five steps in sequence within a transaction:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public Order placeOrder(OrderRequestDTO request) {
    log.info(&quot;Processing new {} order for user: {}&quot;, request.getSide(), request.getUserId());

    // 1. Validate ticker exists and is ACTIVE in asset catalog
    validateTicker(request.getTicker());

    // 2. Validate balance for BUY orders via Wallet API
    if (request.getSide() == OrderSide.BUY) {
        validateBalance(request);
    }

    // 3. Persist order with PENDING status
    Order order = Order.builder()
            .userId(request.getUserId())
            .ticker(request.getTicker().toUpperCase())
            .quantity(request.getQuantity())
            .price(request.getPrice())
            .side(request.getSide())
            .status(OrderStatus.PENDING)
            .build();

    order = orderRepository.save(order);

    // 4. Send to B3 via RabbitMQ
    b3MessageProducer.sendToB3(mapToB3Message(order));

    // 5. Publish PENDING event to Kafka for wallet and other consumers
    orderEventProducer.sendOrderEvent(order.mapToEvent());

    return order;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why validate ticker before balance?&lt;/strong&gt; It's cheaper to fail fast on a non-existent ticker (one network call) than to validate balance first and then discover the ticker is invalid. Order of validations matters for UX and for minimizing unnecessary load on downstream services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Design decision:&lt;/strong&gt; The Kafka event is published with &lt;code&gt;PENDING&lt;/code&gt; status right after creation. This allows &lt;code&gt;trading-broker-wallet&lt;/code&gt; to immediately block the balance, without waiting for B3's result. This is the essence of &lt;strong&gt;eventual consistency&lt;/strong&gt; — each service reacts to the event in its own time.&lt;/p&gt;
&lt;h3 id=&quot;5-b3-integration-rabbitmq&quot;&gt;5. B3 Integration — RabbitMQ&lt;/h3&gt;
&lt;p&gt;Communication with the Matching Engine uses RabbitMQ for a specific reason: &lt;strong&gt;delivery guarantee&lt;/strong&gt;. Even if the Matching Engine is temporarily unavailable, the message stays in the queue and is processed when it comes back.&lt;/p&gt;
&lt;h4 id=&quot;infrastructure-configuration&quot;&gt;Infrastructure configuration&lt;/h4&gt;
&lt;p&gt;All RabbitMQ infrastructure is declared as Spring beans — ensuring queues, exchanges and bindings exist before any message flows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean
public DirectExchange brokerExchange() {
    return new DirectExchange(exchangeName);
}

@Bean
public Queue orderOutQueue() {
    return QueueBuilder.durable(queueIn)
            .withArgument(&quot;x-dead-letter-exchange&quot;, &quot;dlx.broker.orders&quot;)
            .withArgument(&quot;x-dead-letter-routing-key&quot;, &quot;dlq-broker-to-b3&quot;)
            .build();
}

@Bean
public Binding orderOutBinding() {
    return BindingBuilder.bind(orderOutQueue())
            .to(brokerExchange())
            .with(routingKey);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The dead letter &lt;code&gt;withArgument&lt;/code&gt; ensures messages that fail are redirected to the DLQ instead of simply being discarded.&lt;/p&gt;
&lt;h4 id=&quot;producer-sending-to-b3&quot;&gt;Producer — sending to B3&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void sendToB3(B3OrderRequestDTO message) {
    log.info(&quot;Sending order {} to B3 via exchange: {} | routing-key: {}&quot;,
            message.getOrderId(), exchange, routingKey);
    rabbitTemplate.convertAndSend(exchange, routingKey, message);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important decision:&lt;/strong&gt; the message is sent to the &lt;strong&gt;exchange&lt;/strong&gt; with the &lt;strong&gt;routing key&lt;/strong&gt;, not directly to the queue. This respects the RabbitMQ routing model and allows adding new consumers without changing the producer.&lt;/p&gt;
&lt;h4 id=&quot;consumer-receiving-b3-feedback&quot;&gt;Consumer — receiving B3 feedback&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@RabbitListener(queues = &quot;${app.rabbitmq.queue-out}&quot;)
public void receiveFeedback(B3OrderResponseDTO response) {
    log.info(&quot;Feedback received from B3 for order: {} | Status: {}&quot;,
            response.getOrderId(), response.getStatus());
    try {
        orderStatusService.updateOrderStatus(response);
    } catch (Exception e) {
        log.error(&quot;Failed to update status for order {}: {}&quot;,
                response.getOrderId(), e.getMessage(), e);
        throw e; // RabbitMQ applies retry/DLQ policy
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;throw e&lt;/code&gt; is intentional: rethrowing the exception allows RabbitMQ to apply its retry policy instead of silently discarding the message.&lt;/p&gt;
&lt;h3 id=&quot;6-status-update-orderstatusservice&quot;&gt;6. Status Update — OrderStatusService&lt;/h3&gt;
&lt;p&gt;When B3 feedback arrives, &lt;code&gt;OrderStatusService&lt;/code&gt; updates the order state and publishes the second Kafka event:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void updateOrderStatus(B3OrderResponseDTO response) {
    Order order = orderRepository.findById(Long.valueOf(response.getOrderId()))
            .orElseThrow(() -&amp;gt; new RuntimeException(&quot;Order not found: &quot; + response.getOrderId()));

    order.setStatus(OrderStatus.valueOf(response.getStatus()));
    order.setExecutedPrice(response.getExecutedPrice());

    orderRepository.save(order);

    // Notify ecosystem with final status — wallet settles or refunds
    orderEventProducer.sendOrderEvent(order.mapToEvent());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This second Kafka event closes the loop: &lt;code&gt;trading-broker-wallet&lt;/code&gt; consumes it and executes the settlement (&lt;code&gt;FILLED&lt;/code&gt;) or refund (&lt;code&gt;REJECTED&lt;/code&gt;) of the balance blocked in the previous step.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;rest-api&quot;&gt;REST API&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Method &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Endpoint &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;POST &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/orders&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Place a new buy or sell order&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/orders/{id}&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Get order details by ID&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/orders/user/{userId}&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;List all orders for a user&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt; &lt;strong&gt;Swagger UI&lt;/strong&gt;: &lt;code&gt;http://localhost:8088/swagger-ui.html&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-validating-the-execution&quot;&gt;✅ Validating the Execution&lt;/h2&gt;
&lt;p&gt;With the application running locally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Flyway validated the &lt;code&gt;orders&lt;/code&gt; table schema&lt;/li&gt;
&lt;li&gt;✅ Tomcat started on port &lt;code&gt;8088&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ RabbitMQ connected with exchange, queues and DLQ declared&lt;/li&gt;
&lt;li&gt;✅ Swagger UI with 3 documented endpoints&lt;/li&gt;
&lt;li&gt;✅ AssetClient validating ticker before any persistence&lt;/li&gt;
&lt;li&gt;✅ WalletClient validating balance for BUY orders&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-complete-ecosystem&quot;&gt;The Complete Ecosystem&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;trading-broker-order&lt;/code&gt; ready, we have the first end-to-end flow working:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[User] → [Order API] → [Asset API]  (ticker validation)
                    → [Wallet API]  (balance validation)
                    → [B3 Matching Engine] (execution)
                    → [Kafka] → [Wallet API] (custody)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the user's click to the updated wallet balance, through ticker and balance validation, RabbitMQ routing, price matching against Redis, and eventual consistency via Kafka.&lt;/p&gt;
&lt;p&gt;Got any questions about the design decisions or the inter-service orchestration? Drop them in the comments!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt;⬅️ Previous Post: &lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Financial Custody: Managing Balance and Portfolio with Eventual Consistency&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto/trading-broker-order&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17660/the-ecosystem-conductor-orchestrating-market-orders-with-kafka-rabbitmq-and-spring-boot</guid>
<pubDate>Wed, 13 May 2026 00:55:31 +0000</pubDate>
</item>
<item>
<title>Financial Custody: Managing Balance and Portfolio with Eventual Consistency</title>
<link>https://coderlegion.com/17641/financial-custody-managing-balance-and-portfolio-with-eventual-consistency</link>
<description>&lt;p&gt;Hey folks!&lt;/p&gt;
&lt;p&gt;Continuing the &lt;strong&gt;My Broker B3&lt;/strong&gt; series, we've reached one of the most business-logic-rich services in the ecosystem: the &lt;strong&gt;Broker Wallet API&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In previous posts we built the B3 infrastructure (market price sync and matching engine). Now we enter the financial core of the broker. This service is the guardian of the investor's money and assets — it needs to be correct above all else.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-is-the-wallet-api-&quot;&gt;What is the Wallet API?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;trading-broker-wallet&lt;/code&gt; is the &lt;strong&gt;financial custody service&lt;/strong&gt; of the ecosystem. Its responsibility is to react to order lifecycle events and ensure the user's money and assets are managed correctly at each step.&lt;/p&gt;
&lt;p&gt;It doesn't receive direct buy commands — it &lt;strong&gt;listens to Kafka events&lt;/strong&gt; and acts according to each order's status:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Kafka: order-events-v1
         │
    ┌────┴────────────────────────┐
    │                             │
 PENDING                       FILLED / REJECTED
    │                             │
 reserveBalance()          settleOrder() / refundBalance()
    │                             │
 Blocks balance            Settles or refunds
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-is-the-wallet-so-critical-&quot;&gt;Why is the Wallet so Critical?&lt;/h2&gt;
&lt;p&gt;Imagine the following scenario without proper custody:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User has R$ 1,000 available&lt;/li&gt;
&lt;li&gt;Places a buy order for R$ 800&lt;/li&gt;
&lt;li&gt;Before the order is processed, places &lt;strong&gt;another&lt;/strong&gt; order for R$ 500&lt;/li&gt;
&lt;li&gt;Both orders are executed → user ends up with &lt;strong&gt;negative&lt;/strong&gt; balance&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The wallet solves this with the concept of &lt;strong&gt;blocked balance&lt;/strong&gt;: the moment an order enters as &lt;code&gt;PENDING&lt;/code&gt;, the value is immediately reserved. The available balance is always &lt;code&gt;balance - blockedBalance&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-tech-stack-1&quot;&gt;️ Tech Stack&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Usage&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Java 21&lt;/strong&gt; + &lt;strong&gt;Spring Boot 3.5.11&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Service core&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;MySQL&lt;/strong&gt; + &lt;strong&gt;Flyway&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Operational persistence and versioning&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Kafka&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Order event consumption&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Data Redis&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Real-time prices for portfolio valuation&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SpringDoc OpenAPI&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Swagger UI documentation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-the-data-model&quot;&gt;️ The Data Model&lt;/h2&gt;
&lt;p&gt;Before diving into the logic, it's important to understand the three entities that support the service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Account
├── userId (UNIQUE)
├── balance          ← total balance (includes blocked amount)
├── blockedBalance   ← amount reserved for PENDING orders
└── currency

Account (1) ──▶ (N) Position
                     ├── ticker
                     ├── quantity
                     └── averagePrice

WalletTransaction (audit log)
├── orderId
├── userId
├── transactionType  ← RESERVE | SETTLEMENT | REFUND
└── amount
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;blockedBalance&lt;/code&gt; is the central piece of custody. The &lt;code&gt;balance&lt;/code&gt; never goes below zero — only &lt;code&gt;blockedBalance&lt;/code&gt; goes up and down as orders progress through their lifecycle.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-order-lifecycle-in-the-wallet&quot;&gt;The Order Lifecycle in the Wallet&lt;/h2&gt;
&lt;h3 id=&quot;step-1-pending-reserving-the-balance&quot;&gt;Step 1 — PENDING: Reserving the Balance&lt;/h3&gt;
&lt;p&gt;When an order is created, it arrives as &lt;code&gt;PENDING&lt;/code&gt;. The wallet must &lt;strong&gt;immediately block&lt;/strong&gt; the maximum amount that could be debited (quantity × limit price):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void reserveBalance(OrderEventDTO event) {
    BigDecimal amountToReserve = event.getPrice().multiply(event.getQuantity());

    Account account = accountRepository.findByUserId(event.getUserId())
            .orElseThrow(() -&amp;gt; new RuntimeException(&quot;Account not found for user: &quot; + event.getUserId()));

    // Available = balance - blockedBalance
    BigDecimal availableBalance = account.getAvailableBalance();

    if (availableBalance.compareTo(amountToReserve) &amp;lt; 0) {
        throw new RuntimeException(&quot;Insufficient balance to reserve funds.&quot;);
    }

    // Only blockedBalance increases — total balance stays intact
    account.setBlockedBalance(account.getBlockedBalance().add(amountToReserve));
    accountRepository.save(account);

    // Audit record
    walletTransactionRepository.save(WalletTransaction.builder()
            .userId(event.getUserId())
            .orderId(event.getOrderId())
            .transactionType(TransactionType.RESERVE)
            .amount(amountToReserve)
            .build());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important point:&lt;/strong&gt; the total &lt;code&gt;balance&lt;/code&gt; doesn't change at this step. Only &lt;code&gt;blockedBalance&lt;/code&gt; increases. The money is still in the account — it's just reserved.&lt;/p&gt;
&lt;h3 id=&quot;step-2a-filled-settling-the-order&quot;&gt;Step 2a — FILLED: Settling the Order&lt;/h3&gt;
&lt;p&gt;The order was executed by the Matching Engine. Now the money actually leaves. But here's a subtlety: &lt;strong&gt;the executed price may differ from the reserved price&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Example: user placed a buy order with a limit of R$ 30.00, but the market executed at R$ 29.50. The reserved amount was R$ 300 (10 shares × R$ 30), but the actual executed amount was R$ 295 (10 × R$ 29.50). The R$ 5.00 difference automatically becomes available again.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void settleOrder(OrderEventDTO event) {
    BigDecimal totalReserved = event.getPrice().multiply(event.getQuantity());
    BigDecimal totalExecuted = event.getExecutedPrice().multiply(event.getQuantity());

    Account account = accountRepository.findByUserId(event.getUserId())
            .orElseThrow(() -&amp;gt; new RuntimeException(&quot;Account not found&quot;));

    // Release the initial block
    BigDecimal newBlockedBalance = account.getBlockedBalance().subtract(totalReserved);
    account.setBlockedBalance(newBlockedBalance.compareTo(BigDecimal.ZERO) &amp;lt; 0
            ? BigDecimal.ZERO
            : newBlockedBalance);

    // Debit the actual executed amount from total balance
    account.setBalance(account.getBalance().subtract(totalExecuted));
    accountRepository.save(account);

    // Update asset position
    updatePosition(event, account);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2b-rejected-refunding-the-balance&quot;&gt;Step 2b — REJECTED: Refunding the Balance&lt;/h3&gt;
&lt;p&gt;The order was rejected (price outside market range, ticker not found in Redis, etc.). The blocked money becomes available again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void refundBalance(OrderEventDTO event) {
    BigDecimal amountToRefund = event.getPrice().multiply(event.getQuantity());

    Account account = accountRepository.findByUserId(event.getUserId())
            .orElseThrow(() -&amp;gt; new RuntimeException(&quot;Account not found for refund&quot;));

    // Only subtract from blockedBalance — total balance was never touched
    if (account.getBlockedBalance().compareTo(amountToRefund) &amp;gt;= 0) {
        account.setBlockedBalance(account.getBlockedBalance().subtract(amountToRefund));
    } else {
        log.warn(&quot;Refund amount exceeds blocked balance for order {}&quot;, event.getOrderId());
        account.setBlockedBalance(BigDecimal.ZERO);
    }

    accountRepository.save(account);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;weighted-average-price-calculation&quot;&gt;Weighted Average Price Calculation&lt;/h2&gt;
&lt;p&gt;When a purchase is settled, we need to update the user's position in that asset. The weighted average price ensures that purchases made at different times are correctly reflected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;New Average Price = (Current Cost + New Cost) / (Current Qty + New Qty)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private void updatePosition(OrderEventDTO event, Account account) {
    Position position = positionRepository
            .findByAccountIdAndTicker(account.getId(), event.getTicker())
            .orElse(Position.builder()
                    .account(account)
                    .ticker(event.getTicker())
                    .quantity(BigDecimal.ZERO)
                    .averagePrice(BigDecimal.ZERO)
                    .build());

    boolean isSell = &quot;SELL&quot;.equalsIgnoreCase(event.getSide());

    if (isSell) {
        BigDecimal newQuantity = position.getQuantity().subtract(event.getQuantity());
        if (newQuantity.compareTo(BigDecimal.ZERO) &amp;lt;= 0) {
            positionRepository.delete(position);
            return;
        }
        position.setQuantity(newQuantity);
        // Average price stays the same on sell
    } else {
        // BUY: recalculate weighted average price
        BigDecimal currentCost = position.getAveragePrice().multiply(position.getQuantity());
        BigDecimal newCost = event.getExecutedPrice().multiply(event.getQuantity());
        BigDecimal totalQuantity = position.getQuantity().add(event.getQuantity());
        BigDecimal newAveragePrice = currentCost.add(newCost)
                .divide(totalQuantity, 4, RoundingMode.HALF_UP);

        position.setQuantity(totalQuantity);
        position.setAveragePrice(newAveragePrice);
    }

    positionRepository.save(position);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Practical example:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Purchase 1: 10 PETR4 shares at R$ 30.00 → average price = R$ 30.00&lt;/li&gt;
&lt;li&gt;Purchase 2: 5 PETR4 shares at R$ 33.00 → average price = (300 + 165) / 15 = &lt;strong&gt;R$ 31.00&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;critical-bugs-fixed&quot;&gt;Critical Bugs Fixed&lt;/h2&gt;
&lt;p&gt;During the code review, I identified issues that would cause silent failures in production:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Guaranteed NPE on first deposit&lt;/strong&gt;&lt;br&gt;
New account created without initializing &lt;code&gt;blockedBalance&lt;/code&gt;. The first call to &lt;code&gt;getAvailableBalance()&lt;/code&gt; would throw &lt;code&gt;NullPointerException&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ❌ Before
Account.builder()
    .balance(BigDecimal.ZERO)
    // blockedBalance missing → NPE

// ✅ After
Account.builder()
    .balance(BigDecimal.ZERO)
    .blockedBalance(BigDecimal.ZERO)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. Withdraw validated total balance, not available balance&lt;/strong&gt;&lt;br&gt;
Allowed withdrawing money that was blocked in pending orders:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ❌ Before — allows withdrawing reserved funds
if (account.getBalance().compareTo(amount) &amp;lt; 0)

// ✅ After — validates only what's available
if (account.getAvailableBalance().compareTo(amount) &amp;lt; 0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. SELL increased position instead of decreasing it&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;updatePosition()&lt;/code&gt; method always added quantity, completely ignoring the &lt;code&gt;side&lt;/code&gt; field. Selling 10 shares would add 10 to the position.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Kafka consumer silently swallowed exceptions&lt;/strong&gt;&lt;br&gt;
Errors were logged but the offset was committed — the message was discarded without retry. Now the consumer rethrows the exception so Kafka can apply its retry policy correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;idempotency&quot;&gt;Idempotency&lt;/h2&gt;
&lt;p&gt;To prevent duplicates in case of Kafka reprocessing, we added a database constraint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- V4 migration
ALTER TABLE wallet_transactions
    ADD CONSTRAINT uq_order_transaction UNIQUE (order_id, transaction_type);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the same &lt;code&gt;PENDING&lt;/code&gt; event arrives twice, the second insert will fail with a constraint violation — preventing the balance from being blocked twice for the same order.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;rest-api-1&quot;&gt;REST API&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Method &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Endpoint &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;POST &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/wallet/{userId}/deposit&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Deposit funds&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;POST &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/wallet/{userId}/withdraw&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Withdraw available funds&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/wallet/{userId}/summary&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Summary: balance + positions + total equity&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/wallet/{userId}/positions&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;List asset positions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/wallet/{userId}/transactions&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Transaction history&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt; &lt;strong&gt;Swagger UI&lt;/strong&gt;: &lt;code&gt;http://localhost:8085/swagger-ui.html&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-validating-the-execution-1&quot;&gt;✅ Validating the Execution&lt;/h2&gt;
&lt;p&gt;With the application running locally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Flyway applied all 4 migrations successfully&lt;/li&gt;
&lt;li&gt;✅ Hibernate validated the schema&lt;/li&gt;
&lt;li&gt;✅ Kafka consumer connected and subscribed to &lt;code&gt;order-events-v1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ Consumer group &lt;code&gt;trading-broker-wallet&lt;/code&gt; synced&lt;/li&gt;
&lt;li&gt;✅ Swagger UI with 5 documented endpoints at &lt;code&gt;http://localhost:8085/swagger-ui.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-s-next-&quot;&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;With the Wallet ready to react to order events, the next step is building the &lt;strong&gt;trading-broker-order&lt;/strong&gt; — the orchestrator that will manage the complete order lifecycle:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Receive buy/sell intent via REST&lt;/li&gt;
&lt;li&gt;Validate the ticker against &lt;code&gt;trading-broker-asset&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Save the order as &lt;code&gt;PENDING&lt;/code&gt; and publish to Kafka&lt;/li&gt;
&lt;li&gt;Send the order to the Matching Engine via RabbitMQ&lt;/li&gt;
&lt;li&gt;Consume the result and update the final status&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once that service is ready, we'll have the first complete end-to-end flow: from the user's click all the way to the updated wallet balance.&lt;/p&gt;
&lt;p&gt;Got any questions about the custody logic or the PENDING → FILLED → REJECTED cycle? Drop them in the comments!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series-1&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt;⬅️ Previous Post: &lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;The Heart of B3: Building the Matching Engine with RabbitMQ and Redis&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto/trading-broker-wallet&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17641/financial-custody-managing-balance-and-portfolio-with-eventual-consistency</guid>
<pubDate>Tue, 12 May 2026 20:43:57 +0000</pubDate>
</item>
<item>
<title>Microservices Are Easy to Create and Manage</title>
<link>https://coderlegion.com/17052/microservices-are-easy-to-create-and-manage</link>
<description>&lt;p&gt;I bumped into the claim somewhere on LinkedIn and that got me thinking why some people may think so while others consider this a huge overstatement. As I often see, such contradictions emerge because people judge microservices from very different &lt;strong&gt;implied&lt;/strong&gt; contexts. In this article I make a draft attempt to make the context &lt;strong&gt;explicit&lt;/strong&gt; and common to reconcile these contradictions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NB&lt;/strong&gt;: However, this article is more for my own structured thinking sake I would welcome and gladly discuss your experience in comments.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Microservices are easy to create and manage when there are few of them (2-3-5), the core product functionality is stable within 6+ months (no inter-service contracts change), the workflows are a few (2-3) and simple, and load is small (e.g. 10-50 RPS). When one of these characteristics is disturbed the very high immanent complexity of microservices spills out and quickly becomes very hard to manage.&lt;/p&gt;
&lt;p&gt;Nevertheless, the statement &quot;microservices are easy to create and manage&quot; is very attractive as a target state, and this writing aims at defining the ways to get close to that target. After all, making complex things simple and manageable is a part of our craft - software engineering.&lt;/p&gt;
&lt;h4 id=&quot;why-microservices-are-so-complex&quot;&gt;Why Microservices Are So Complex&lt;/h4&gt;
&lt;p&gt;Reliable microservices require very complex implementation. Compared to correctly done modularized monoliths, the number of extra dependencies is like 20+. I list the core ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Inter-services contracts: (e.g. OpenAPI, AsyncAPI), 1 contract per each service inputs and outputs - creation and alignment with 2 other dependents (inputs and outputs), contract versioning, contract enforcement.&lt;/li&gt;
&lt;li&gt;Messaging: the facility (e.g. RabbitMQ) - at least one separate unit to deploy, .&lt;/li&gt;
&lt;li&gt;Deployment complexity: requires versioning match and Kubernetes for more than 2-3 services.&lt;/li&gt;
&lt;li&gt;Operations - debugging and operational observability: structured JSON logging for business flow, error logging for system exceptions, monitoring for service health (Prometheus, Grafana and all that stuff).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;microservices-dependencies-reference&quot;&gt;Microservices Dependencies Reference&lt;/h3&gt;
&lt;p&gt;Here are 4 large operational groups of dependencies for further analysis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Boundary Integrity&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;strict contracts: simultaneous cross-service contract evolution, backward compatibility, contract versioning pressure.&lt;/li&gt;
&lt;li&gt;minimal shared state: data duplication, duplicated data consistency.&lt;/li&gt;
&lt;li&gt;stable ownership boundaries: clear service responsibility and dependency isolation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Purpose: Prevent uncontrolled coupling and assumption leakage between services. Core question: &quot;What is allowed to cross service boundaries?&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow Determinism&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;deterministic workflows: complexity controlled with explicit state machines and behavioral patterns (strategy etc).&lt;/li&gt;
&lt;li&gt;idempotent operations: retries, duplicate suppression and replay safety (preserved state, correlation IDs).&lt;/li&gt;
&lt;li&gt;async/event workflows: application event/domain facts contract enforcement and evolution management.&lt;/li&gt;
&lt;li&gt;error handling: clean separation between domain errors and application errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Purpose: Ensure distributed processes behave predictably despite retries, delays, duplicates, and failures. Core question: &quot;Will the system converge to the same valid state under real-world distributed conditions?&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugging and Operational Observability&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Purpose: Make distributed behavior explainable and diagnosable. Core question: &quot;Can we reconstruct what happened and why?&quot;.&lt;/p&gt;
&lt;p&gt;This area gradually expands into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;logs: (structures JSON, application, exception logs).&lt;/li&gt;
&lt;li&gt;correlation IDs.&lt;/li&gt;
&lt;li&gt;distributed traces.&lt;/li&gt;
&lt;li&gt;operational and business metrics.&lt;/li&gt;
&lt;li&gt;workflow/data lineage.&lt;/li&gt;
&lt;li&gt;auditability.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Delivery&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Purpose: Reduce operational complexity by deployment standardization and automation across services. Core question: &quot;Can every service be operated the same way?”&lt;/p&gt;
&lt;p&gt;This includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;standardized deployment model.&lt;/li&gt;
&lt;li&gt;runtime conventions.&lt;/li&gt;
&lt;li&gt;CI/CD.&lt;/li&gt;
&lt;li&gt;infrastructure topology.&lt;/li&gt;
&lt;li&gt;service templates.&lt;/li&gt;
&lt;li&gt;operational procedures.&lt;/li&gt;
&lt;li&gt;resilience standards.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;suggestion&quot;&gt;Suggestion&lt;/h3&gt;
&lt;p&gt;Reducing complexity in these four groups by standardizing and automating gradually and systematically paves the way to the &quot;microservices are easy to create and manage&quot; target state however hardly achievable for complex and rapidly evolving systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Basic Reading&lt;/strong&gt;: Sam Newman, &quot;Monolith To Microservices&quot;, &quot;Building Microservices&quot;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17052/microservices-are-easy-to-create-and-manage</guid>
<pubDate>Fri, 08 May 2026 11:22:50 +0000</pubDate>
</item>
<item>
<title>Market Data Integrator: Consuming Real-Time Data with Python, MongoDB, and Kafka</title>
<link>https://coderlegion.com/16542/market-data-integrator-consuming-real-time-data-with-python-mongodb-and-kafka</link>
<description>&lt;p&gt;Hey folks!&lt;/p&gt;
&lt;p&gt;Continuing the &lt;strong&gt;My Broker B3&lt;/strong&gt; series, today we'll build the first microservice in the ecosystem: the &lt;strong&gt;trading-broker-market-data&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This service has a simple and well-defined responsibility: &lt;strong&gt;fetch real quotes for Brazilian assets from the Brapi API and distribute them to the ecosystem&lt;/strong&gt; — persisting the history in MongoDB and publishing updated prices to Kafka for other services to consume.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-this-service-s-role-in-the-ecosystem&quot;&gt;️ This Service's Role in the Ecosystem&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[brapi.dev] ◀── HTTP ── [broker-market-data-api]
                                  │
                    ┌─────────────┴──────────────┐
                    ▼                             ▼
             MongoDB                           Kafka
        (price_history)          (trading-assets-market-data-v1)
                                               │
                                    [broker-asset-api] (consumer)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It's the entry point for real data into the system. Without it, the ecosystem operates on static prices.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;mvp-focus&quot;&gt;MVP Focus&lt;/h2&gt;
&lt;p&gt;The goal here is clear: &lt;strong&gt;have real quotes available in Kafka&lt;/strong&gt; for other services to react to. In this phase, I prioritized:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scheduled ingestion of 50 assets (Blue Chips, REITs and ETFs)&lt;/li&gt;
&lt;li&gt;Historical persistence in MongoDB for audit&lt;/li&gt;
&lt;li&gt;Kafka event publishing for downstream consumers&lt;/li&gt;
&lt;li&gt;Rate limit protection for the Brapi API (free plan)&lt;/li&gt;
&lt;li&gt;Immediate execution on service startup&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-tech-stack-2&quot;&gt;️ Tech Stack&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Usage&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Python 3.12&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Service runtime&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;requests&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;HTTP client for Brapi API&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;pymongo&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;MongoDB persistence&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;confluent-kafka&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Kafka producer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;schedule&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Periodic scheduling&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;python-dotenv&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Environment variables&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-the-implementation&quot;&gt;️ The Implementation&lt;/h2&gt;
&lt;h3 id=&quot;the-ingestion-loop&quot;&gt;The Ingestion Loop&lt;/h3&gt;
&lt;p&gt;The heart of the service is the &lt;code&gt;run_ingestion()&lt;/code&gt; function, which iterates over the watchlist, fetches each asset's price and distributes the data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def run_ingestion():
    &quot;&quot;&quot;Fetches market data from Brapi, persists to MongoDB and publishes to Kafka.&quot;&quot;&quot;
    logger.info(&quot;Market data ingestion job started&quot;)

    token = os.getenv(&quot;BRAPI_TOKEN&quot;)
    if not token:
        logger.error(&quot;BRAPI_TOKEN not set. Aborting.&quot;)
        return

    # Producer created per run to avoid crash on import if Kafka is unavailable
    producer = Producer({
        'bootstrap.servers': os.getenv(&quot;KAFKA_BOOTSTRAP_SERVERS&quot;, &quot;localhost:9092&quot;),
        'client.id': 'broker-market-data-api'
    })

    mongo = MongoRepository()
    success_count = 0
    error_count = 0

    for ticker in WATCHLIST:
        try:
            url = f&quot;https://brapi.dev/api/quote/{ticker}?token={token}&quot;
            response = requests.get(url, timeout=10)
            response.raise_for_status()

            result = response.json()[&quot;results&quot;][0]
            payload = {
                &quot;ticker&quot;: result.get(&quot;symbol&quot;),
                &quot;name&quot;: result.get(&quot;longName&quot;),
                &quot;short_name&quot;: result.get(&quot;shortName&quot;),
                &quot;price&quot;: result.get(&quot;regularMarketPrice&quot;),
                &quot;volume&quot;: result.get(&quot;regularMarketVolume&quot;),
                &quot;updated_at&quot;: result.get(&quot;regularMarketTime&quot;)
            }

            # 1. Persist historical record in MongoDB
            mongo.save(payload.copy())

            # 2. Publish event to Kafka
            producer.produce(
                topic=TOPIC_NAME,
                key=ticker,
                value=json.dumps(payload).encode('utf-8'),
                callback=delivery_report
            )
            producer.poll(0)

            success_count += 1
            time.sleep(0.5)  # Rate limiting — respect Brapi free plan

        except Exception as e:
            error_count += 1
            logger.error(f&quot;Error processing ticker {ticker}: {e}&quot;)

    producer.flush()
    logger.info(f&quot;Ingestion completed: {success_count} succeeded, {error_count} failed&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important design decisions:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why create the Producer inside the function?&lt;/strong&gt; If it were at module level, an unavailable Kafka at &lt;code&gt;import&lt;/code&gt; time would crash the script before executing any logic. Creating it inside the function means a Kafka failure doesn't prevent execution — only that run fails.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;try/catch&lt;/code&gt; per ticker?&lt;/strong&gt; If one asset fails (timeout, invalid ticker), the loop continues for the rest. The &lt;code&gt;error_count&lt;/code&gt; at the end shows how many failed without stopping the work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The &lt;code&gt;time.sleep(0.5)&lt;/code&gt;&lt;/strong&gt; — respects the rate limit of Brapi's free plan. For 50 assets, the full run takes ~25 seconds.&lt;/p&gt;
&lt;h3 id=&quot;the-scheduling&quot;&gt;The Scheduling&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if __name__ == &quot;__main__&quot;:
    logger.info(f&quot;Starting — sync every {SYNC_INTERVAL_MINUTES} minutes&quot;)

    # Run immediately on startup, then on schedule
    run_ingestion()

    schedule.every(SYNC_INTERVAL_MINUTES).minutes.do(run_ingestion)

    while True:
        schedule.run_pending()
        time.sleep(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why run immediately on startup?&lt;/strong&gt; If the service restarts, we don't want to wait 30 minutes for data in Kafka. The first execution is immediate, subsequent ones follow the schedule.&lt;/p&gt;
&lt;h3 id=&quot;the-kafka-payload&quot;&gt;The Kafka Payload&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;ticker&quot;: &quot;PETR4&quot;,
  &quot;name&quot;: &quot;Petróleo Brasileiro S.A. - Petrobras&quot;,
  &quot;short_name&quot;: &quot;PETROBRAS PN&quot;,
  &quot;price&quot;: 35.50,
  &quot;volume&quot;: 15234567,
  &quot;updated_at&quot;: 1714234567
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Topic:&lt;/strong&gt; &lt;code&gt;trading-assets-market-data-v1&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Key:&lt;/strong&gt; asset symbol (e.g. &lt;code&gt;PETR4&lt;/code&gt;) — ensures events for the same asset go to the same partition&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-watchlist&quot;&gt;The Watchlist&lt;/h2&gt;
&lt;p&gt;50 assets covering the main B3 segments:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;WATCHLIST = [
    # Blue Chips
    &quot;PETR4&quot;, &quot;PETR3&quot;, &quot;VALE3&quot;, &quot;ITUB4&quot;, &quot;BBDC4&quot;, &quot;BBAS3&quot;, ...
    # REITs / FIIs
    &quot;MXRF11&quot;, &quot;HGLG11&quot;, &quot;KNIP11&quot;, &quot;XPLG11&quot;, ...
    # ETFs
    &quot;BOVA11&quot;, &quot;IVVB11&quot;
]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;security-token-out-of-code&quot;&gt;Security: Token Out of Code&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# .env — never committed
BRAPI_TOKEN=your_token_here
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# application — no fallback, required variable
token = os.getenv(&quot;BRAPI_TOKEN&quot;)
if not token:
    logger.error(&quot;BRAPI_TOKEN not set. Aborting.&quot;)
    return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Never expose credentials as default values in public repositories.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-validating-the-execution-2&quot;&gt;✅ Validating the Execution&lt;/h2&gt;
&lt;p&gt;With the service running locally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ First run executes immediately on startup&lt;/li&gt;
&lt;li&gt;✅ MongoDB receives documents with &lt;code&gt;created_at&lt;/code&gt; automatically&lt;/li&gt;
&lt;li&gt;✅ Kafka receives messages with key = ticker&lt;/li&gt;
&lt;li&gt;✅ Scheduler waits 30 minutes for the next round&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-s-next--1&quot;&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;broker-market-data-api&lt;/code&gt; publishing quotes to Kafka, the next step is &lt;code&gt;b3-market-sync-api&lt;/code&gt; — the Java service that consumes these events and synchronizes prices to Redis, feeding the Matching Engine with real-time data.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series-2&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt;⬅️ Previous Post: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/infrastructure-as-code-deploying-a-financial-ecosystem-with-docker-compose-2p3d&quot;&gt;Infrastructure as Code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;➡️ Next Post: Syncing the Real Market: Consuming Brapi and Feeding Redis with Spring Boot&lt;/p&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto/trading-broker-market-data&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16542/market-data-integrator-consuming-real-time-data-with-python-mongodb-and-kafka</guid>
<pubDate>Sun, 03 May 2026 20:33:47 +0000</pubDate>
</item>
<item>
<title>The Evolution of a SaaS Architecture</title>
<link>https://coderlegion.com/16467/the-evolution-of-a-saas-architecture</link>
<description>&lt;p&gt;&lt;img src=&quot;https://cdn.jaiswalankit.com.np/The%20Evoluation%20of%20a%20SaaS/The%20Evolution%20of%20a%20SaaS%20-%201.jpg&quot; alt=&quot;The Evolution of a SaaS Architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;Building a scalable SaaS platform is a journey of continuous learning and strategic evolution. In modern software engineering, the most successful platforms are those built with an &quot;Evolutionary Architecture&quot; mindset. This means designing your systems to embrace change, allowing your technical capabilities to grow in perfect harmony with your user base and business milestones.&lt;/p&gt;
&lt;p&gt;When launching a new platform, the primary objective is learning about the market, discovering user needs, and establishing product-market fit. A successful architecture supports this discovery phase by prioritizing developer velocity and adaptability. By aligning your infrastructure with your current traffic levels, you ensure that engineering resources are focused entirely on delivering value to the user.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jaiswalankit.com.np/The%20Evoluation%20of%20a%20SaaS/The%20Evolution%20of%20a%20SaaS%20-%202.png&quot; alt=&quot;Match Your Tech to Your Traffic&quot;&gt;&lt;/p&gt;
&lt;p&gt;The strategy for building a highly resilient, long-lasting platform relies on a phased, educational approach. You map your infrastructure investments directly to the milestones you achieve in user growth. We can organize this philosophy into three essential pillars of technical growth:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Build for validation:&lt;/strong&gt; Design systems that allow for rapid prototyping, incredibly fast deployment cycles, and immediate user feedback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Upgrade for stability:&lt;/strong&gt; As user acquisition accelerates, introduce foundational scaling techniques to ensure high availability and consistent response times.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Architect for scale:&lt;/strong&gt; When your platform reaches enterprise levels of engagement, systematically decouple components to unlock distributed, specialized computing power.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By adopting this progressive framework, you maintain a highly efficient engineering budget while gracefully supporting increased demand. Here is a comprehensive, deeply technical roadmap for guiding your application's journey from a unified monolith to an intelligent, event-driven microservices ecosystem.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;phase-1-the-monolithic-approach&quot;&gt;Phase 1: The Monolithic Approach&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jaiswalankit.com.np/The%20Evoluation%20of%20a%20SaaS/The%20Evolution%20of%20a%20SaaS%20-%203.png&quot; alt=&quot;Getting to know the market&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Traffic Milestone: 0 to 1,000 users.&lt;/strong&gt; &lt;strong&gt;The Tech: A Modular Monolith on a dedicated virtual environment.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At the inception of a project, engineering speed is your greatest asset. The goal is to translate ideas into functional code as efficiently as possible to gather real-world data. A monolithic architecture is uniquely equipped to provide this rapid iteration cycle, allowing you to ship new features daily without the overhead of managing distributed systems.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jaiswalankit.com.np/The%20Evoluation%20of%20a%20SaaS/The%20Evolution%20of%20a%20SaaS%20-%204.png&quot; alt=&quot;Phase 1: The Visual&quot;&gt;&lt;/p&gt;
&lt;p&gt;In a well-designed monolithic architecture, all core functionalities your user interface serving, business logic, background jobs, and data access layers reside within a single, unified codebase and operate within the same memory space. &lt;/p&gt;
&lt;p&gt;This unified approach offers tremendous advantages for an evolving product. Deployments are straightforward, often requiring just a single CI/CD pipeline step. Because all modules share the same memory, internal function calls are lightning-fast, entirely avoiding the latency and complexity of network-based API communication. Furthermore, observing the system is beautifully simple; developers can trace a user's entire journey through the application by examining a single, centralized stream of logs. &lt;/p&gt;
&lt;p&gt;To make the most of this phase, engineering teams should focus on building a &lt;strong&gt;Modular Monolith&lt;/strong&gt;. By strictly enforcing logical boundaries within the code such as separating the user management module from the billing module you create a clean, well-organized system. This discipline not only makes the codebase easier to understand and test but also perfectly positions the application for future architectural evolution. &lt;/p&gt;
&lt;h3 id=&quot;phase-2-horizontal-scaling-and-the-12-factor-app&quot;&gt;Phase 2: Horizontal Scaling and the 12-Factor App&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jaiswalankit.com.np/The%20Evoluation%20of%20a%20SaaS/The%20Evolution%20of%20a%20SaaS%20-%205.png&quot; alt=&quot;Handling the Load&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Traffic Milestone: 1,000 to 10,000 users.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As your platform's value resonates with the market, user engagement naturally increases. You will begin to observe higher CPU utilization and increased memory consumption as your server actively processes concurrent user requests. This is a positive milestone it signals that your platform is thriving and it is time to expand your compute capacity.&lt;/p&gt;
&lt;p&gt;To handle increased throughput gracefully, the architecture shifts from vertical scaling (adding a larger server) to horizontal scaling (adding multiple identical servers). By containerizing your application packaging the code, runtime, and dependencies into standardized units using Docker you can launch multiple instances of your application simultaneously. &lt;/p&gt;
&lt;p&gt;These instances sit behind an &lt;strong&gt;Application Load Balancer&lt;/strong&gt;. The load balancer acts as an intelligent traffic director, continuously monitoring the health of your application instances and distributing incoming HTTP requests evenly across the fleet. If traffic surges, you can simply instruct your cloud provider to spin up additional containers to share the workload.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Architectural Shift:&lt;/strong&gt; Transitioning to a horizontally scaled environment introduces a brilliant engineering concept: &lt;strong&gt;Statelessness&lt;/strong&gt;, a core principle of the 12-Factor App methodology. Because a user's requests might be routed to Server A on their first click and Server B on their second, your application servers can no longer rely on storing local data. &lt;/p&gt;
&lt;p&gt;To resolve this, engineers externalize the state. Session data, temporary cache, and user tokens are migrated to a centralized, high-speed, in-memory datastore like &lt;strong&gt;Redis&lt;/strong&gt;. By decoupling the state from the application servers, any server in your fleet can seamlessly pick up a request and process it with full context, creating a deeply resilient, highly available application tier.&lt;/p&gt;
&lt;h3 id=&quot;phase-3-optimizing-the-data-layer&quot;&gt;Phase 3: Optimizing the Data Layer&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jaiswalankit.com.np/The%20Evoluation%20of%20a%20SaaS/The%20Evolution%20of%20a%20SaaS%20-%206.png&quot; alt=&quot;Protecting the Database&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Traffic Milestone: 10,000 to 100,000 users.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Expanding your API server fleet handles computational load beautifully, but data-intensive applications eventually shift the demand to the persistent storage layer. In a thriving SaaS environment, ensuring the relational database remains responsive is paramount for a seamless user experience. &lt;/p&gt;
&lt;p&gt;Before structurally changing the database, modern engineering teams focus on &lt;strong&gt;Intelligent Caching&lt;/strong&gt;. By leveraging the Redis infrastructure introduced in Phase 2, developers can intercept frequent, heavy database queries. If thousands of users are loading the same global analytics dashboard, the database performs the complex calculation once, stores the result in Redis, and serves the subsequent thousands of requests directly from memory in a fraction of a millisecond. Mastering cache invalidation strategies ensuring users always see fresh data becomes a rewarding engineering focus at this stage.&lt;/p&gt;
&lt;p&gt;As engagement deepens, you can further enhance data availability by implementing a &lt;strong&gt;Read/Write Split&lt;/strong&gt; at the database level. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Master Node:&lt;/strong&gt; The primary database instance becomes highly specialized, dedicated entirely to processing data modifications handling the &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; transactions ensuring absolute data integrity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Read Replicas:&lt;/strong&gt; You introduce synchronized copies of your database. The application intelligently routes all complex, read-only queries (&lt;code&gt;SELECT&lt;/code&gt;) to these replicas. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This architecture prevents heavy reporting queries from consuming the resources needed for transactional operations. Your application learns to handle &quot;eventual consistency,&quot; understanding that there might be a few milliseconds of replication delay between writing to the master and reading from the replica. This elegant separation of concerns ensures that the data layer remains incredibly robust, even during your highest traffic events.&lt;/p&gt;
&lt;h3 id=&quot;phase-4-unlocking-the-event-driven-ecosystem&quot;&gt;Phase 4: Unlocking the Event-Driven Ecosystem&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jaiswalankit.com.np/The%20Evoluation%20of%20a%20SaaS/The%20Evolution%20of%20a%20SaaS%20-%207.png&quot; alt=&quot;The Async Microservice Shift&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Traffic Milestone: 100,000+ users.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As your platform reaches enterprise-level utilization, you will likely offer advanced, resource-intensive features. Your application might generate comprehensive data exports, encode high-resolution media, or orchestrate complex interactions with external Artificial Intelligence models. &lt;/p&gt;
&lt;p&gt;Handling these monumental tasks synchronously forcing the user to wait while the main API processes the data monopolizes server threads and impacts the experience of other concurrent users. This is the perfect moment to evolve your architecture by isolating these specific workloads into independent &lt;strong&gt;Microservices&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jaiswalankit.com.np/The%20Evoluation%20of%20a%20SaaS/The%20Evolution%20of%20a%20SaaS%20-%208.png&quot; alt=&quot;Event-Driven Scaling&quot;&gt;&lt;/p&gt;
&lt;p&gt;Instead of processing heavy tasks on the spot, the main application adopts an &lt;strong&gt;Asynchronous, Event-Driven Architecture&lt;/strong&gt;. The platform delegates the heavy lifting by utilizing a highly durable &lt;strong&gt;Message Broker&lt;/strong&gt; (such as Apache Kafka, Amazon SQS, or RabbitMQ).&lt;/p&gt;
&lt;p&gt;The interaction becomes a seamless, non-blocking flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The API Gateway:&lt;/strong&gt; The user requests a complex AI generation. The main API immediately returns a &lt;code&gt;202 Accepted&lt;/code&gt; response, assuring the user that the process has begun.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Event Stream:&lt;/strong&gt; The API securely publishes a message payload containing the job details into a dedicated queue. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Worker Fleet:&lt;/strong&gt; Independent, specialized microservices designed specifically for CPU-intensive or GPU-intensive workloads listen to this queue. They pick up the messages and process the tasks silently in the background, updating the main database or notifying the user via WebSockets upon completion.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This architectural leap provides the ultimate capability: &lt;strong&gt;Granular, Event-Driven Scaling&lt;/strong&gt;. Because your services are decoupled, you can optimize your infrastructure costs brilliantly. If there is a massive backlog of video encoding jobs, you can configure your cloud environment to automatically spin up 50 instances of the specific &quot;Video Worker&quot; microservice to drain the queue quickly, while keeping your main API fleet operating at a stable, cost-effective baseline.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;cultivating-an-ecosystem-of-continuous-learning&quot;&gt;Cultivating an Ecosystem of Continuous Learning&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jaiswalankit.com.np/The%20Evoluation%20of%20a%20SaaS/The%20Evolution%20of%20a%20SaaS%20-%209.png&quot; alt=&quot;Grow Your Tech&quot;&gt;&lt;/p&gt;
&lt;p&gt;The evolution from a single server to a distributed microservices architecture is a fascinating journey of continuous technical improvement. The most effective engineering teams understand that architecture is never truly &quot;finished&quot;; it is a living ecosystem that adapts to serve the user.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start with a Modular Monolith&lt;/strong&gt; to maximize learning, validate your market hypothesis, and establish clean domain boundaries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Introduce Load Balancing and Statelessness&lt;/strong&gt; to guarantee high availability and consistent performance as your audience grows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimize Data Retrieval&lt;/strong&gt; through intelligent memory caching and database replication, ensuring your persistence layer is always responsive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embrace Event-Driven Microservices&lt;/strong&gt; strategically, isolating complex workloads to unlock specialized computing power and granular scalability.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To guide this evolution effectively, implementing deep &lt;strong&gt;Observability&lt;/strong&gt; is key. By integrating tools like OpenTelemetry, Prometheus, and distributed tracing, you grant your engineering team the ability to visualize how data flows through the system. These metrics serve as the guiding light, telling you exactly when and where to apply the next architectural evolution. &lt;/p&gt;
&lt;p&gt;Build systems that serve the present beautifully, while actively designing the foundational agility required for the innovations of tomorrow.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jaiswalankit.com.np/The%20Evoluation%20of%20a%20SaaS/The%20Evolution%20of%20a%20SaaS%20-%2010.png&quot; alt=&quot;Let's Connect&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Looking to strategically evolve your platform's architecture? Let's Connect!&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;I am a Senior Full Stack AI Engineer specializing in the design, deployment, and optimization of highly resilient, cloud-agnostic SaaS platforms and intelligent, event-driven applications.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16467/the-evolution-of-a-saas-architecture</guid>
<pubDate>Sun, 03 May 2026 04:04:26 +0000</pubDate>
</item>
<item>
<title>Building a Microservices Ecosystem: Stock Brokerage Simulator (My Broker B3)</title>
<link>https://coderlegion.com/16327/building-a-microservices-ecosystem-stock-brokerage-simulator-my-broker-b3</link>
<description>&lt;p&gt;Hey folks!&lt;/p&gt;
&lt;p&gt;I'm starting a series of articles to document the development of &lt;strong&gt;My Broker B3&lt;/strong&gt; — a personal project where I apply advanced software engineering concepts, distributed systems and messaging to simulate the real-world operation of a stock brokerage integrated with B3 (the Brazilian stock exchange).&lt;/p&gt;
&lt;p&gt;The goal isn't to build a production system, but rather a &lt;strong&gt;POC (Proof of Concept)&lt;/strong&gt; that validates real-time order processing, financial custody management and market data orchestration using an event-driven architecture.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-the-ecosystem-overview&quot;&gt;️ The Ecosystem Overview&lt;/h2&gt;
&lt;p&gt;The project was designed following the microservices philosophy with a hybrid stack — each service uses the technology that best fits its function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;                    [broker-gateway-api]
                           │
          ┌────────────────┼────────────────┐
          ▼                ▼                ▼
   [broker-order-api] [broker-wallet] [broker-asset]
          │                │
     RabbitMQ           Kafka (order-events-v1)
          │
   [b3-matching-engine]
          │
       Redis ◀── [b3-market-sync-api] ◀── [brapi.dev]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-services&quot;&gt;The Services&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Broker Side&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Service &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Language &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Responsibility&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-identity-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Authentication, JWT, user management&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-gateway-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;API Gateway, routing, JWT validation&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-order-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Order lifecycle orchestration&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-wallet-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Financial custody, balance and positions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-asset-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Asset catalog and internal market data&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-market-data-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Python &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Quote ingestion from Brapi → MongoDB + Kafka&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;broker-report-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Reports and history&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;B3 Side (Simulator)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Service &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Language &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Responsibility&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;b3-matching-engine-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Order execution, price matching&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;b3-market-sync-api&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Java &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Price synchronization to Redis&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-communication-strategy&quot;&gt;⚙️ Communication Strategy&lt;/h2&gt;
&lt;p&gt;One of the most important decisions in the project was choosing &lt;strong&gt;when to use synchronous communication and when to use asynchronous&lt;/strong&gt;. The rule was simple: if the response is needed now to continue the flow, use REST. If it can wait, use messaging.&lt;/p&gt;
&lt;h3 id=&quot;synchronous-rest-feign-&quot;&gt;Synchronous (REST/Feign)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;broker-order-api&lt;/code&gt; → &lt;code&gt;broker-asset-api&lt;/code&gt;: validates ticker before creating order&lt;/li&gt;
&lt;li&gt;&lt;code&gt;broker-order-api&lt;/code&gt; → &lt;code&gt;broker-wallet-api&lt;/code&gt;: validates balance before processing buy&lt;/li&gt;
&lt;li&gt;&lt;code&gt;broker-identity-api&lt;/code&gt; → &lt;code&gt;broker-wallet-api&lt;/code&gt;: creates wallet account on registration&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;asynchronous-rabbitmq-&quot;&gt;Asynchronous (RabbitMQ)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;broker-order-api&lt;/code&gt; → &lt;code&gt;b3-matching-engine-api&lt;/code&gt;: sends orders for execution&lt;/li&gt;
&lt;li&gt;&lt;code&gt;b3-matching-engine-api&lt;/code&gt; → &lt;code&gt;broker-order-api&lt;/code&gt;: execution feedback (FILLED/REJECTED)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;asynchronous-kafka-&quot;&gt;Asynchronous (Kafka)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;broker-market-data-api&lt;/code&gt; → topic &lt;code&gt;assets-market-data-v1&lt;/code&gt; → &lt;code&gt;broker-asset-api&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;broker-order-api&lt;/code&gt; → topic &lt;code&gt;order-events-v1&lt;/code&gt; → &lt;code&gt;broker-wallet-api&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;persistence-strategy&quot;&gt;Persistence Strategy&lt;/h2&gt;
&lt;p&gt;Each service uses the database that best fits its nature:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Service(s) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Why&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;MySQL&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;identity, wallet, order, asset &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Transactional data with strong consistency&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;b3-matching-engine &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;B3 core with execution history&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;MongoDB&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;broker-market-data &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Quote history — flexible schema&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Redis&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;broker-asset, b3-matching-engine &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Real-time price cache for fast decisions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-order-flow-end-to-end&quot;&gt;The Order Flow — End to End&lt;/h2&gt;
&lt;p&gt;To materialize the architecture, here's what happens when a user places a buy order:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;1. POST /api/v1/orders
        │
        ├─ Validate balance → broker-wallet-api (REST)
        ├─ Persist PENDING → MySQL
        ├─ Publish PENDING → Kafka (broker-wallet blocks balance)
        └─ Send to B3 → RabbitMQ

2. B3 processes, looks up price in Redis
        │
        ├─ FILLED/REJECTED → RabbitMQ → broker-order-api
        ├─ Update status → MySQL
        └─ Publish final status → Kafka (broker-wallet settles/refunds)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the user's click to the updated wallet balance, through validation, price matching and eventual consistency.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-you-ll-find-in-the-series&quot;&gt;What You'll Find in the Series&lt;/h2&gt;
&lt;p&gt;Each post documents the construction of a service, focusing on technical decisions, problems encountered and how they were solved:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Overview&lt;/strong&gt; ← you are here&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt; — Docker Compose with 12+ containers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-market-data-api&lt;/strong&gt; — Python, Brapi, MongoDB, Kafka&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;b3-market-sync-api&lt;/strong&gt; — Price synchronization to Redis&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;b3-matching-engine-api&lt;/strong&gt; — The B3 execution engine&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-asset-api&lt;/strong&gt; — Asset catalog and cache&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-wallet-api&lt;/strong&gt; — Financial custody&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-order-api&lt;/strong&gt; — The order conductor&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-identity-api&lt;/strong&gt; — Authentication and JWT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broker-gateway-api&lt;/strong&gt; — API Gateway&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Feel free to leave feedback or questions in the comments!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series-3&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16327/building-a-microservices-ecosystem-stock-brokerage-simulator-my-broker-b3</guid>
<pubDate>Fri, 01 May 2026 13:50:40 +0000</pubDate>
</item>
<item>
<title>From Monolith to Modular: A Strangler Migration Playbook</title>
<link>https://coderlegion.com/16070/from-monolith-to-modular-a-strangler-migration-playbook</link>
<description>&lt;p&gt;Every successful application eventually outgrows its original architecture. What started as a clean, well-structured monolith becomes a tangled web of dependencies where changing one feature risks breaking three others. Deployments become risky, test suites take forever, and developers spend more time navigating the codebase than writing new features.&lt;/p&gt;
&lt;p&gt;The instinct is to rewrite. Do not. Rewrites are how engineering teams burn months of effort and deliver something that, at best, does what the old system already did. The better approach is incremental migration: systematically extracting capabilities from the monolith while keeping the system running in production the entire time.&lt;/p&gt;
&lt;p&gt;I have led these migrations across multiple companies — taking legacy codebases with 15+ modules, hundreds of database tables, and years of accumulated technical debt and transforming them into modular, maintainable architectures without downtime. This article is the playbook.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-monoliths-become-painful&quot;&gt;Why Monoliths Become Painful&lt;/h3&gt;
&lt;p&gt;Monoliths are not inherently bad. They are fast to build, easy to deploy, and simple to reason about — at small scale. The problems emerge gradually:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deployment coupling:&lt;/strong&gt; Every change, no matter how small, requires deploying the entire application. A CSS fix and a payment logic change ship in the same deployment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dependency tangles:&lt;/strong&gt; Module A depends on Module B which depends on Module C which depends on Module A. Circular dependencies make changes unpredictable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test suite slowdown:&lt;/strong&gt; Running the full test suite takes 30+ minutes. Developers start skipping tests. Bugs escape to production.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Team bottlenecks:&lt;/strong&gt; Multiple teams working on the same codebase create merge conflicts, code review backlogs, and release coordination overhead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scaling limitations:&lt;/strong&gt; You cannot scale the payment processing component independently of the user profile component. Everything scales (or fails) together.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-strangler-fig-pattern&quot;&gt;The Strangler Fig Pattern&lt;/h3&gt;
&lt;p&gt;The strangler fig pattern, named after the plant that gradually envelops its host tree, is the safest approach to migrating away from a monolith. The strategy:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identify a capability&lt;/strong&gt; to extract from the monolith&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build the new implementation&lt;/strong&gt; alongside the old one&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Route traffic&lt;/strong&gt; to the new implementation (gradually or all at once)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remove the old implementation&lt;/strong&gt; once the new one is proven&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repeat&lt;/strong&gt; for the next capability&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The monolith continues to run throughout. There is no big bang cutover. Each migration step is small, reversible, and independently testable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;phase-1-understanding-what-you-have&quot;&gt;Phase 1: Understanding What You Have&lt;/h3&gt;
&lt;p&gt;Before you extract anything, you need a map of the current system.&lt;/p&gt;
&lt;h4 id=&quot;dependency-analysis&quot;&gt;Dependency Analysis&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Laravel: Find all cross-module dependencies
# Look for imports/use statements between modules
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;laravel-module-boundary-analysis&quot;&gt;Laravel Module Boundary Analysis&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

class AnalyzeModuleDependencies extends Command
{
    protected $signature = 'modules:analyze';

    public function handle(): void
    {
        $modules = $this-&amp;gt;getModuleDirectories();
        $dependencies = [];

        foreach ($modules as $module) {
            $files = File::allFiles(app_path($module));
            $imports = [];

            foreach ($files as $file) {
                $content = $file-&amp;gt;getContents();
                preg_match_all('/use App\\\\(\w+)\\\\/', $content, $matches);
                $imports = array_merge($imports, $matches[1]);
            }

            $externalDeps = array_unique(
                array_filter($imports, fn ($dep) =&amp;gt; $dep !== $module)
            );

            $dependencies[$module] = $externalDeps;
        }

        foreach ($dependencies as $module =&amp;gt; $deps) {
            $this-&amp;gt;info(&quot;{$module} depends on: &quot; . implode(', ', $deps));
        }

        $this-&amp;gt;detectCircularDependencies($dependencies);
    }

    private function detectCircularDependencies(array $dependencies): void
    {
        foreach ($dependencies as $moduleA =&amp;gt; $depsA) {
            foreach ($depsA as $moduleB) {
                if (isset($dependencies[$moduleB]) &amp;amp;&amp;amp; in_array($moduleA, $dependencies[$moduleB])) {
                    $this-&amp;gt;warn(&quot;Circular dependency: {$moduleA} &amp;lt;-&amp;gt; {$moduleB}&quot;);
                }
            }
        }
    }

    private function getModuleDirectories(): array
    {
        return collect(File::directories(app_path()))
            -&amp;gt;map(fn ($dir) =&amp;gt; basename($dir))
            -&amp;gt;filter(fn ($dir) =&amp;gt; !in_array($dir, ['Console', 'Exceptions', 'Http', 'Providers']))
            -&amp;gt;values()
            -&amp;gt;all();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;node-js-dependency-mapping&quot;&gt;Node.js Dependency Mapping&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import fs from 'fs';
import path from 'path';

interface ModuleDependency {
  module: string;
  dependsOn: string[];
  files: number;
  linesOfCode: number;
}

function analyzeModuleDependencies(srcDir: string): ModuleDependency[] {
  const modules = fs.readdirSync(srcDir, { withFileTypes: true })
    .filter((d) =&amp;gt; d.isDirectory())
    .map((d) =&amp;gt; d.name);

  return modules.map((moduleName) =&amp;gt; {
    const moduleDir = path.join(srcDir, moduleName);
    const files = getAllFiles(moduleDir, ['.ts', '.js']);
    const imports: string[] = [];
    let totalLines = 0;

    files.forEach((file) =&amp;gt; {
      const content = fs.readFileSync(file, 'utf-8');
      totalLines += content.split('\n').length;

      const importRegex = /from\s+['&quot;]\.\.\/(\w+)/g;
      let match;
      while ((match = importRegex.exec(content)) !== null) {
        if (match[1] !== moduleName) {
          imports.push(match[1]);
        }
      }
    });

    return {
      module: moduleName,
      dependsOn: [...new Set(imports)],
      files: files.length,
      linesOfCode: totalLines,
    };
  });
}

function getAllFiles(dir: string, extensions: string[]): string[] {
  const results: string[] = [];
  const entries = fs.readdirSync(dir, { withFileTypes: true });

  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      results.push(...getAllFiles(fullPath, extensions));
    } else if (extensions.some((ext) =&amp;gt; entry.name.endsWith(ext))) {
      results.push(fullPath);
    }
  }

  return results;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;phase-2-establish-module-boundaries&quot;&gt;Phase 2: Establish Module Boundaries&lt;/h3&gt;
&lt;p&gt;Before extracting services, enforce boundaries within the monolith. This is the most important step and the one most teams skip.&lt;/p&gt;
&lt;h4 id=&quot;laravel-module-service-providers-and-contracts&quot;&gt;Laravel: Module Service Providers and Contracts&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// app/Billing/Contracts/BillingService.php
namespace App\Billing\Contracts;

interface BillingService
{
    public function createInvoice(string $customerId, array $lineItems): Invoice;
    public function processPayment(string $invoiceId, array $paymentMethod): PaymentResult;
    public function getCustomerBalance(string $customerId): Money;
}

// app/Billing/BillingServiceProvider.php
namespace App\Billing;

use Illuminate\Support\ServiceProvider;

class BillingServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this-&amp;gt;app-&amp;gt;bind(
            Contracts\BillingService::class,
            Services\BillingServiceImpl::class
        );
    }
}

// app/Orders/Services/OrderService.php
namespace App\Orders\Services;

use App\Billing\Contracts\BillingService;

class OrderService
{
    public function __construct(private BillingService $billing) {}

    public function completeOrder(Order $order): void
    {
        $invoice = $this-&amp;gt;billing-&amp;gt;createInvoice(
            $order-&amp;gt;customer_id,
            $order-&amp;gt;lineItems-&amp;gt;toArray()
        );

        $order-&amp;gt;update(['invoice_id' =&amp;gt; $invoice-&amp;gt;id, 'status' =&amp;gt; 'invoiced']);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;node-js-module-interfaces&quot;&gt;Node.js: Module Interfaces&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// src/billing/contracts.ts
export interface BillingService {
  createInvoice(customerId: string, lineItems: LineItem[]): Promise&amp;lt;Invoice&amp;gt;;
  processPayment(invoiceId: string, method: PaymentMethod): Promise&amp;lt;PaymentResult&amp;gt;;
  getCustomerBalance(customerId: string): Promise&amp;lt;Money&amp;gt;;
}

// src/billing/index.ts
export { BillingServiceImpl as BillingService } from './services/billing-service';
export type { BillingService as BillingServiceInterface } from './contracts';

// src/orders/services/order-service.ts
import type { BillingService } from '../../billing/contracts';

export class OrderService {
  constructor(private billing: BillingService) {}

  async completeOrder(order: Order): Promise&amp;lt;void&amp;gt; {
    const invoice = await this.billing.createInvoice(
      order.customerId,
      order.lineItems
    );

    await this.orderRepo.update(order.id, {
      invoiceId: invoice.id,
      status: 'invoiced',
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key insight: modules communicate through interfaces, not direct implementation references. When you later extract the billing module into its own service, you replace the implementation behind the interface. The calling code does not change.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;phase-3-extract-the-first-service&quot;&gt;Phase 3: Extract the First Service&lt;/h3&gt;
&lt;p&gt;Choose a module that is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Well-bounded (few incoming dependencies)&lt;/li&gt;
&lt;li&gt;Independently valuable&lt;/li&gt;
&lt;li&gt;Not on the critical path (if the extraction has problems, it should not take down the whole system)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-routing-layer&quot;&gt;The Routing Layer&lt;/h4&gt;
&lt;p&gt;Use a proxy or feature flag to route traffic between the monolith and the new service:&lt;/p&gt;
&lt;h4 id=&quot;laravel-switchable-implementation&quot;&gt;Laravel: Switchable Implementation&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// app/Billing/BillingServiceProvider.php
class BillingServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this-&amp;gt;app-&amp;gt;bind(Contracts\BillingService::class, function ($app) {
            $useNewService = config('features.billing_service_v2', false);

            if ($useNewService) {
                return new HttpBillingServiceClient(
                    config('services.billing.url'),
                    config('services.billing.api_key'),
                );
            }

            return new Services\BillingServiceImpl();
        });
    }
}

// The HTTP client talks to the extracted billing service
class HttpBillingServiceClient implements Contracts\BillingService
{
    public function __construct(
        private string $baseUrl,
        private string $apiKey,
    ) {}

    public function createInvoice(string $customerId, array $lineItems): Invoice
    {
        $response = Http::withToken($this-&amp;gt;apiKey)
            -&amp;gt;timeout(10)
            -&amp;gt;retry(3, 100)
            -&amp;gt;post(&quot;{$this-&amp;gt;baseUrl}/api/invoices&quot;, [
                'customer_id' =&amp;gt; $customerId,
                'line_items' =&amp;gt; $lineItems,
            ]);

        if ($response-&amp;gt;failed()) {
            throw new BillingServiceException(&quot;Failed to create invoice: &quot; . $response-&amp;gt;body());
        }

        return Invoice::fromArray($response-&amp;gt;json());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;node-js-strategy-pattern-switch&quot;&gt;Node.js: Strategy Pattern Switch&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import type { BillingService } from './contracts';
import { LocalBillingService } from './services/billing-service';
import { RemoteBillingService } from './services/remote-billing-service';

export function createBillingService(): BillingService {
  if (process.env.BILLING_SERVICE_V2 === 'true') {
    return new RemoteBillingService(
      process.env.BILLING_SERVICE_URL!,
      process.env.BILLING_SERVICE_API_KEY!
    );
  }

  return new LocalBillingService();
}

class RemoteBillingService implements BillingService {
  constructor(
    private baseUrl: string,
    private apiKey: string,
  ) {}

  async createInvoice(customerId: string, lineItems: LineItem[]): Promise&amp;lt;Invoice&amp;gt; {
    const response = await fetch(`${this.baseUrl}/api/invoices`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`,
      },
      body: JSON.stringify({ customer_id: customerId, line_items: lineItems }),
    });

    if (!response.ok) {
      throw new Error(`Billing service error: ${response.status}`);
    }

    return response.json();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;phase-4-data-migration-strategy&quot;&gt;Phase 4: Data Migration Strategy&lt;/h3&gt;
&lt;p&gt;The hardest part of service extraction is data ownership. When the billing module becomes its own service, who owns the billing tables?&lt;/p&gt;
&lt;h4 id=&quot;shared-database-phase&quot;&gt;Shared Database Phase&lt;/h4&gt;
&lt;p&gt;Initially, the new service reads from the same database as the monolith. This avoids data migration complexity during the initial extraction.&lt;/p&gt;
&lt;h4 id=&quot;data-sync-phase&quot;&gt;Data Sync Phase&lt;/h4&gt;
&lt;p&gt;Introduce event-based data synchronisation. The new service maintains its own datastore, kept in sync via events:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;class BillingDataSyncListener
{
    public function handle(CustomerUpdated $event): void
    {
        Http::post(config('services.billing.url') . '/api/sync/customer', [
            'customer_id' =&amp;gt; $event-&amp;gt;customerId,
            'name' =&amp;gt; $event-&amp;gt;name,
            'email' =&amp;gt; $event-&amp;gt;email,
        ]);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;full-ownership-phase&quot;&gt;Full Ownership Phase&lt;/h4&gt;
&lt;p&gt;The new service owns its data completely. The monolith no longer writes to billing tables. All access goes through the service API.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;phase-5-zero-downtime-cutover&quot;&gt;Phase 5: Zero-Downtime Cutover&lt;/h3&gt;
&lt;p&gt;The cutover from monolith to new service should be invisible to users:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shadow mode:&lt;/strong&gt; Both implementations run. The new service processes requests but responses come from the monolith. Compare outputs for correctness.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Canary rollout:&lt;/strong&gt; Route 5% of traffic to the new service. Monitor error rates, latency, and correctness. Gradually increase.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Full rollout:&lt;/strong&gt; Route 100% to the new service. Keep the monolith implementation for 2 weeks as a rollback option.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cleanup:&lt;/strong&gt; Remove the old implementation from the monolith.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;class BillingServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this-&amp;gt;app-&amp;gt;bind(Contracts\BillingService::class, function () {
            $strategy = config('features.billing_migration_phase');

            return match ($strategy) {
                'monolith' =&amp;gt; new Services\BillingServiceImpl(),
                'shadow' =&amp;gt; new ShadowBillingService(
                    primary: new Services\BillingServiceImpl(),
                    shadow: new HttpBillingServiceClient(/*...*/),
                ),
                'canary' =&amp;gt; new CanaryBillingService(
                    legacy: new Services\BillingServiceImpl(),
                    modern: new HttpBillingServiceClient(/*...*/),
                    percentage: (int) config('features.billing_canary_percentage', 5),
                ),
                'modern' =&amp;gt; new HttpBillingServiceClient(/*...*/),
                default =&amp;gt; new Services\BillingServiceImpl(),
            };
        });
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;measuring-migration-success&quot;&gt;Measuring Migration Success&lt;/h3&gt;
&lt;p&gt;Track these metrics throughout the migration:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Deployment frequency &lt;/td&gt;&lt;td&gt;Should increase as modules become independent&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Lead time for changes &lt;/td&gt;&lt;td&gt;Should decrease as codebase complexity drops&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Test suite runtime &lt;/td&gt;&lt;td&gt;Should decrease per module&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Incidents per deployment &lt;/td&gt;&lt;td&gt;Should decrease with smaller change sets&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Mean time to recovery &lt;/td&gt;&lt;td&gt;Should decrease with independent rollback capability&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;production-results-from-past-migrations&quot;&gt;Production Results from Past Migrations&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;Before (Monolith) &lt;/th&gt;&lt;th&gt;After (Modular)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Deployment frequency &lt;/td&gt;&lt;td&gt;1–2 per week &lt;/td&gt;&lt;td&gt;3–5 per day&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Full test suite runtime &lt;/td&gt;&lt;td&gt;35 minutes &lt;/td&gt;&lt;td&gt;8 minutes (per module)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Average deploy risk score &lt;/td&gt;&lt;td&gt;High &lt;/td&gt;&lt;td&gt;Low (per module)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Time to onboard new developer &lt;/td&gt;&lt;td&gt;3 weeks &lt;/td&gt;&lt;td&gt;1 week&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Query execution time &lt;/td&gt;&lt;td&gt;1.8s average &lt;/td&gt;&lt;td&gt;180ms average (60% reduction)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;System performance &lt;/td&gt;&lt;td&gt;Baseline &lt;/td&gt;&lt;td&gt;40% improvement&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;key-takeaways&quot;&gt;Key Takeaways&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never rewrite. Always migrate incrementally.&lt;/strong&gt; The strangler fig pattern lets you replace the monolith piece by piece while keeping the system running.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Establish boundaries before extracting.&lt;/strong&gt; Enforce module interfaces within the monolith first. This makes extraction a configuration change, not a rewrite.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start with a low-risk module.&lt;/strong&gt; Pick something well-bounded and non-critical for your first extraction. Build confidence before tackling core business logic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use feature flags for cutover.&lt;/strong&gt; Shadow mode, canary rollouts, and instant rollback make the migration safe. Never do a big bang switchover.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solve data ownership explicitly.&lt;/strong&gt; Shared database → event-based sync → full ownership is the safest data migration path.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Measure continuously.&lt;/strong&gt; Deployment frequency, test runtime, and incident rate tell you whether the migration is actually improving things.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Migrating from a monolith to a modular architecture is not a technical project — it is an engineering discipline applied over weeks and months. The strangler fig pattern makes it safe. Interface-first module design makes it clean. Feature flags make it reversible. And continuous measurement makes it accountable.&lt;/p&gt;
&lt;p&gt;Whether you are working in Laravel or Node.js, the approach is the same: understand what you have, enforce boundaries, extract behind interfaces, migrate data carefully, and cut over gradually. The monolith did not become painful overnight, and the fix will not happen overnight either. But each step makes the system better, and the cumulative impact is transformative.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16070/from-monolith-to-modular-a-strangler-migration-playbook</guid>
<pubDate>Tue, 28 Apr 2026 14:20:43 +0000</pubDate>
</item>
<item>
<title>From Monolith to Microservices: How Bounded Context Improves AI-Driven Development</title>
<link>https://coderlegion.com/13269/from-monolith-to-microservices-how-bounded-context-improves-ai-driven-development</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;When we first launched our vibe coding platform, the architecture started as a single monolithic application. Initially this approach allowed us to move quickly and ship features fast.&lt;/p&gt;
&lt;p&gt;But as the platform grew, the warning signs appeared.&lt;/p&gt;
&lt;p&gt;The codebase became increasingly complex, new features were harder to implement, and development velocity began to slow down. What once felt manageable started to evolve into a tightly coupled system that was difficult to reason about.&lt;/p&gt;
&lt;p&gt;To solve this, we made a strategic decision: refactor the platform from a monolith into a microservices architecture.&lt;/p&gt;
&lt;p&gt;In this article we explain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Why monolithic architectures struggle in AI-driven systems&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How Domain-Driven Design (DDD) helps define system boundaries&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why bounded contexts are critical for AI agents&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How we split our platform into eight microservices&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This architectural shift significantly improved both developer productivity and AI agent performance.&lt;/p&gt;
&lt;h3 id=&quot;understanding-domain-driven-design&quot;&gt;Understanding Domain-Driven Design&lt;/h3&gt;
&lt;p&gt;The concept of Domain-Driven Design (DDD) was introduced by Eric Evans in the influential book Domain‑Driven Design: Tackling Complexity in the Heart of Software.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/caoz8ghxy7zj5bjpl12s.jpg&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Although the book was published in 2003, its ideas remain extremely relevant today. Many modern architectural patterns—including microservices—are heavily inspired by DDD principles.&lt;/p&gt;
&lt;p&gt;DDD focuses on one core idea:&lt;/p&gt;
&lt;p&gt;Software architecture should mirror the structure of the business domain. Instead of organizing systems purely around technical layers, DDD encourages teams to structure software around real business capabilities and concepts.&lt;/p&gt;
&lt;p&gt;While doing domain distillation, we detected the 6 main bounded contexts that can act as independent services: project planning, AI coding, project deployment, version control, observability, account and billing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvz5nkx1t2ol5tlnhe2e.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-monolith-vs-microservices-debate&quot;&gt;The Monolith vs Microservices Debate&lt;/h3&gt;
&lt;p&gt;For years developers have debated a fundamental architectural question:&lt;/p&gt;
&lt;p&gt;Should applications remain monolithic, or should they be broken into microservices?&lt;/p&gt;
&lt;p&gt;Monoliths have advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Simpler deployments&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easier early development&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fewer distributed systems challenges&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, as systems scale, monoliths often become harder to maintain. Large codebases accumulate complexity and eventually turn into what developers often call a &quot;Big Ball of Mud.&quot;&lt;/p&gt;
&lt;p&gt;Today the conversation has evolved even further. We are no longer building systems only for human developers. We are building systems that AI agents interact with, modify, and reason about. And this introduces a new challenge.&lt;/p&gt;
&lt;h3 id=&quot;why-monolithic-architectures-confuse-ai-agents&quot;&gt;Why Monolithic Architectures Confuse AI Agents&lt;/h3&gt;
&lt;p&gt;AI agents rely heavily on clear context boundaries.When a system grows into a large monolithic repository:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Domain concepts overlap&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Terminology becomes ambiguous&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Architectural boundaries blur&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This makes it harder for AI systems to correctly interpret intent. For example, an AI agent might receive a command such as:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Create a project.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In a large monolith, the term project might refer to several different things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A planning workspace&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A source code repository&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A deployment environment&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without clear boundaries, the AI agent must guess the meaning. And guessing leads to errors. This is why architectural clarity matters even more in AI-driven development environments.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-bounded-context-&quot;&gt;What Is a Bounded Context?&lt;/h3&gt;
&lt;p&gt;A Bounded Context is a central concept in Domain-Driven Design. It defines a clear boundary within which a specific domain model applies and terminology has a single meaning.&lt;/p&gt;
&lt;p&gt;Inside a bounded context:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Language is consistent&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Concepts are clearly defined&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Domain logic belongs only to that context&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This creates a shared understanding between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Developers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Domain experts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI agents&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each context effectively becomes its own mini-world with well-defined rules.&lt;/p&gt;
&lt;h4 id=&quot;why-bounded-contexts-improve-ai-development&quot;&gt;Why Bounded Contexts Improve AI Development&lt;/h4&gt;
&lt;p&gt;In large software systems, the same word can mean different things in different domains. While building our AI coding platform, we encountered this problem quickly. Consider the word project.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ik5g0t5d1ceg7mjhw2t.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;When both meanings existed in the same codebase, confusion spread. Not only for engineers—but for AI agents operating within the platform. By introducing bounded contexts, we ensured that each domain defines its own terminology and behavior. This dramatically improved the reliability of both human workflows and AI-generated development tasks.&lt;/p&gt;
&lt;h3 id=&quot;our-microservices-architecture&quot;&gt;Our Microservices Architecture&lt;/h3&gt;
&lt;p&gt;After refactoring the platform using Domain-Driven Design principles, we divided the system into eight bounded contexts, each implemented as a separate microservice.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ygq5pwlprz8dxz3baws.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;This allowed each domain to evolve independently while maintaining clear system boundaries.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Project Planning Context&lt;/strong&gt; - manages planning activities. Within this domain we store: tasks, projects, agent which generate tasks...&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI Coding Context&lt;/strong&gt; - this is where development actually happens. AI coding agents operate in this domain and perform tasks such as: writing code, committing changes, managing development workflows.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Project Deployment Context&lt;/strong&gt; - manages the infrastructure and pipelines required to deploy software built by AI agents.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Version Control Context&lt;/strong&gt; - while platforms like GitHub provide robust version control, we chose to build a lightweight internal system optimized specifically for AI-driven development workflows.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observability Context&lt;/strong&gt; - collects events and analytics from across the platform.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Account and Billing Context&lt;/strong&gt; - manages user accounts, platform credits, payments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API Gateway&lt;/strong&gt; - acts as the central entry point into the platform.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Platform UI&lt;/strong&gt; - provides the user interface that connects all services together.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;avoiding-the-big-ball-of-mud&quot;&gt;Avoiding the Big Ball of Mud&lt;/h3&gt;
&lt;p&gt;Without clear architectural boundaries, complex systems eventually degrade into tightly coupled codebases that are difficult to maintain. Developers often describe this situation as a &quot;Big Ball of Mud.&quot;&lt;/p&gt;
&lt;p&gt;Symptoms typically include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Spaghetti code&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Increasing bugs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Slower development cycles&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Poor onboarding experience&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confusing system behavior&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For AI-driven platforms, the consequences are even worse because agents lose the contextual clarity required to generate reliable code.&lt;/p&gt;
&lt;p&gt;By organizing our platform around bounded contexts and microservices, we achieved several key benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Clear domain ownership&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Faster feature development&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better system scalability&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improved AI agent performance&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More maintainable architecture&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;As AI agents become increasingly involved in software development, architecture design must evolve alongside them. Bounded contexts and microservices are no longer just about scaling engineering teams—they also help AI systems reason about complex software platforms. For us, adopting Domain-Driven Design and microservices architecture transformed a growing monolith into a scalable platform designed for the future of AI-assisted development.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Multiple models are in play on any large project. Yet when code based on distinct models is combined, software becomes buggy, unreliable, and difficult to understand.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;— Eric Evans&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13269/from-monolith-to-microservices-how-bounded-context-improves-ai-driven-development</guid>
<pubDate>Fri, 20 Mar 2026 17:46:34 +0000</pubDate>
</item>
<item>
<title>Escaping the Sync Trap: How I Slashed Latency by 10x in a Django-Rust API Gateway</title>
<link>https://coderlegion.com/13072/escaping-the-sync-trap-how-i-slashed-latency-by-10x-in-a-django-rust-api-gateway</link>
<description>&lt;h3 id=&quot;how-i-diagnosed-and-eliminated-synchronous-bottlenecks-in-a-django-rust-api-gateway-migrating-to-asgi-and-pre-warming-caches-for-millisecond-responses-&quot;&gt;How I diagnosed and eliminated synchronous bottlenecks in a Django-Rust API gateway, migrating to ASGI and pre-warming caches for millisecond responses.&lt;/h3&gt;
&lt;p&gt;When building a high-performance backend, the standard playbook is well-known: &lt;strong&gt;offload heavy computational tasks to faster microservices (like Rust)&lt;/strong&gt; and implement an aggressive caching strategy.&lt;/p&gt;
&lt;p&gt;Recently, I did exactly that. My architecture is built around a &lt;strong&gt;Django REST Framework gateway&lt;/strong&gt; sitting behind &lt;strong&gt;Caddy&lt;/strong&gt;, heavily monitored with &lt;strong&gt;Prometheus&lt;/strong&gt; and &lt;strong&gt;Grafana&lt;/strong&gt;.  &lt;/p&gt;
&lt;p&gt;But despite the raw speed of Rust and my caching layers, my dashboards were flashing red. Latency was spiking to brutal 10-second flatlines for my most critical endpoints. Worse, my observability itself started failing, creating &lt;em&gt;silent blind spots&lt;/em&gt; exactly when I needed data the most.&lt;/p&gt;
&lt;p&gt;Here is the detective story of how I used telemetry to hunt down synchronous traps, migrate to a non-blocking async architecture, and implement proactive pre-warming to bring response times down to the millisecond range — all while reclaiming &lt;strong&gt;30% of my idle CPU&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-architecture-a-gateway-and-its-heavy-lifters&quot;&gt;The Architecture: A Gateway and its Heavy Lifters&lt;/h3&gt;
&lt;p&gt;Before diving into the problem, here is a quick look at my setup.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;          ┌──────────────────────────────┐
          │          Nextcloud           │
          │ (Authenticated Client Calls) │
          └──────────────┬───────────────┘
                         │  JWT / API Key
                         ▼
               ┌────────────────────┐
               │  Django Gateway    │
               │ (ASGI, DRF, Caddy) │
               └──────┬─────────────┘
                      │
     ┌────────────────┴────────────────┐
     │                                 │
     ▼                                 ▼
┌───────────────┐              ┌────────────────┐
│ NDVI Service  │              │ Weather Service│
│ (Rust, 8081)  │              │ (Rust, 8090)   │
│  → Postgres    │              │  → MySQL       │
└───────────────┘              └────────────────┘

       ▲
       │ Prometheus &amp;amp; Grafana
       │ (Observability Stack)
       ▼
   System Telemetry + Metrics
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7vebc0f2a5ldvbhzfubp.png&quot; alt=&quot;The Architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Django&lt;/strong&gt; stays as the public API gateway, accepting requests authenticated via &lt;strong&gt;JWT&lt;/strong&gt; or &lt;strong&gt;API keys&lt;/strong&gt;.&lt;br&gt;
It enforces a shared JSON response envelope containing &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, and &lt;code&gt;errors&lt;/code&gt; to keep all client interactions standardized.&lt;/p&gt;
&lt;p&gt;Specific traffic routes — namely &lt;code&gt;/api/v1/ndvi&lt;/code&gt; and &lt;code&gt;/api/v1/weather/*&lt;/code&gt; — are forwarded directly to my &lt;strong&gt;Rust&lt;/strong&gt; backends:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NDVI microservice&lt;/strong&gt; ingests satellite data into a dedicated &lt;strong&gt;Postgres&lt;/strong&gt; database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weather microservice&lt;/strong&gt; relies on a &lt;strong&gt;MySQL&lt;/strong&gt; database and communicates with external providers like &lt;strong&gt;Open-Meteo&lt;/strong&gt; and &lt;strong&gt;NASA POWER&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My &lt;strong&gt;Nextcloud&lt;/strong&gt; instance acts like any other client, presenting either an &lt;code&gt;Authorization: Bearer&lt;/code&gt; token or an &lt;code&gt;X-API-Key&lt;/code&gt;. Django manages this traffic using a specific &lt;code&gt;nextcloud_hmac&lt;/code&gt; throttle configuration before passing the authorized call down to Rust with the original headers intact.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-false-cure-the-worker-starvation-anomaly&quot;&gt;The False Cure: The Worker Starvation Anomaly&lt;/h3&gt;
&lt;p&gt;To protect the system, I implemented aggressive TTL caching (e.g., 1 hour for schema data, 5 minutes for API tokens). However, once I added traffic, my &lt;strong&gt;Grafana&lt;/strong&gt; dashboard revealed a chaotic reality.&lt;/p&gt;
&lt;p&gt;I saw brutal, perfectly flat 8–10 second latency spikes on key endpoints. Crucially, perfectly timed with these latency spikes, my internal &lt;code&gt;/metrics&lt;/code&gt; request rate dropped to zero.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-diagnosis-the-gateway-caching-itself-to-death&quot;&gt;The Diagnosis: The Gateway Caching Itself to Death&lt;/h3&gt;
&lt;p&gt;The telemetry told a story of hidden synchronous bottlenecks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Trigger:&lt;/strong&gt; When a high-traffic endpoint like &lt;code&gt;farm-weather-current/GET&lt;/code&gt; experienced a cache miss, the Django gateway had to fetch fresh data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Trap:&lt;/strong&gt; My Django deployment was running using &lt;strong&gt;standard synchronous workers&lt;/strong&gt;. It called the Rust service, which then called the external weather API (taking 2.2+ seconds).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Impact (Worker Starvation):&lt;/strong&gt; Because the Django worker was synchronous, it &lt;em&gt;blocked entirely&lt;/em&gt; for those 2.2 seconds. All incoming traffic got stuck in a queue.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-trap-synchronous-gateway-routing&quot;&gt;The Trap: Synchronous Gateway Routing&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import httpx
from django.http import JsonResponse
from django.views import View

async def async_weather_proxy_view(request):
    async with httpx.AsyncClient(timeout=5.0) as client:
        resp = await client.get(&quot;http://weather-service:8090/api/v1/weather-current&quot;)
        resp.raise_for_status()
        data = resp.json()
    return JsonResponse({&quot;status&quot;: &quot;success&quot;, &quot;data&quot;: data, &quot;errors&quot;: None})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had successfully offloaded work to Rust — but my synchronous Django workers completely nullified the speed gains.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-first-fix-embracing-the-non-blocking-gateway&quot;&gt;The First Fix: Embracing the Non-Blocking Gateway&lt;/h3&gt;
&lt;p&gt;I needed to decouple the speed of the gateway from the speed of the external API calls it was routing.&lt;br&gt;
I migrated the Django deployment from &lt;strong&gt;synchronous workers&lt;/strong&gt; to an &lt;strong&gt;ASGI&lt;/strong&gt; (Asynchronous Server Gateway Interface) setup, allowing my gateway to handle requests asynchronously.&lt;/p&gt;
&lt;p&gt;I rewrote my proxy views to use asynchronous HTTP clients like &lt;strong&gt;httpx&lt;/strong&gt;:&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-fix-asynchronous-non-blocking-routing&quot;&gt;The Fix: Asynchronous Non-Blocking Routing&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import httpx
from django.http import JsonResponse

async def async_weather_proxy_view(request):
    # The event loop is freed! Django can serve other requests while waiting
    async with httpx.AsyncClient() as client:
        rust_response = await client.get(&quot;http://weather-service:8090/api/v1/weather-current&quot;)
        
    return JsonResponse({
        &quot;status&quot;: &quot;success&quot;,
        &quot;data&quot;: rust_response.json(),
        &quot;errors&quot;: None
    })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The visual evidence on my dashboards was a massive, instant victory:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Observability Restored:&lt;/strong&gt; The metrics scrape line remained unbroken. Django could finally pause a slow weather request, instantly answer the Prometheus scrape, and resume without blocking.&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;Instant Internal Routing:&lt;/strong&gt; In my initial setup, a simple internal metrics scrape took ~84ms. After the ASGI migration, that duration dropped to &lt;strong&gt;11ms&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-second-fix-proactive-caching&quot;&gt;The Second Fix: Proactive Caching&lt;/h3&gt;
&lt;p&gt;While the infrastructure was now bulletproof, the end-user experience was still occasionally sluggish.&lt;/p&gt;
&lt;p&gt;With an “on-demand” caching strategy, the very first user to request the weather after a 1-hour cache expiration had to pay the &lt;strong&gt;Cache Miss Penalty&lt;/strong&gt; (waiting ~2.2 seconds for the external API).&lt;/p&gt;
&lt;p&gt;To eliminate this, I &lt;strong&gt;decoupled the data-fetching time from the user-request cycle entirely&lt;/strong&gt;.&lt;br&gt;
I implemented a &lt;strong&gt;Proactive Background Pre-warming&lt;/strong&gt; pattern using a background task (like Celery) that runs every 55 minutes, independently fetching slow data and silently overwriting the cache before it expires.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8gk9f4fawwvmmjypt9xe.png&quot; alt=&quot;caching&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-cache-pre-warmer-celery-example-&quot;&gt;The Cache Pre-Warmer (Celery Example)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from celery import shared_task
from django.core.cache import cache
import requests

@shared_task
def pre_warm_weather_cache():
    # Runs in the background every 55 minutes, shielding the user from the 2.2s wait
    response = requests.get(&quot;http://weather-service:8090/api/v1/weather-current&quot;)
    if response.status_code == 200:
        cache.set(&quot;weather_current_data&quot;, response.json(), timeout=3600)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result? The average latency for critical weather endpoints &lt;strong&gt;plummeted from seconds to milliseconds&lt;/strong&gt; as the hot cache permanently took over.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-grand-slam-optimizing-the-rust-build-pipeline&quot;&gt;The Grand Slam: Optimizing the Rust Build Pipeline&lt;/h3&gt;
&lt;p&gt;The final victory of this new architecture came from pure server efficiency.&lt;br&gt;
During deployments, compiling Rust crates (&lt;code&gt;sqlx&lt;/code&gt;, &lt;code&gt;syn&lt;/code&gt;) from scratch was pegging my 4-core server at 100% CPU, artificially causing timeouts.&lt;/p&gt;
&lt;p&gt;To fix this, I implemented &lt;strong&gt;cargo-chef&lt;/strong&gt; in a &lt;strong&gt;multi-stage Dockerfile&lt;/strong&gt; to strictly cache Rust dependencies.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;multi-stage-dockerfile-for-the-rust-microservice&quot;&gt;Multi-stage Dockerfile for the Rust Microservice&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;FROM rust:1.88-slim AS chef
USER root
RUN cargo install cargo-chef
WORKDIR /app

FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json

FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
# Docker caches this heavy dependency build!
RUN cargo chef cook --release --recipe-path recipe.json
COPY . .
RUN cargo build --release --bin weather-service

FROM debian:bookworm-slim AS runtime
WORKDIR /app
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y libssl-dev ca-certificates
COPY --from=builder /app/target/release/weather-service /usr/local/bin/
EXPOSE 8090
ENTRYPOINT [&quot;/usr/local/bin/weather-service&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Between ASGI, background caching, and Docker layer caching, my total &lt;strong&gt;Node CPU&lt;/strong&gt; now rests comfortably between &lt;strong&gt;11% and 13%&lt;/strong&gt;.&lt;br&gt;
I fundamentally reclaimed &lt;strong&gt;30% of my total server compute capacity&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-finding-the-next-bottleneck&quot;&gt;Conclusion: Finding the Next Bottleneck&lt;/h3&gt;
&lt;p&gt;Building high-performance API gateways is an ongoing journey of shifting bottlenecks.&lt;/p&gt;
&lt;p&gt;By relying strictly on my telemetry, I proved that &lt;strong&gt;synchronous workers nullify microservice speed&lt;/strong&gt;, validated the immense power of &lt;strong&gt;ASGI&lt;/strong&gt;, and eliminated &lt;strong&gt;cache miss penalties&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;With the gateway running unburdened, my dashboards have revealed one final bottleneck — a 6-to-8 second delay on my token generation endpoint.&lt;br&gt;
Because my CPU is mostly idle, I know exactly what this is: a &lt;strong&gt;database connection pool limitation&lt;/strong&gt; in the Rust service.&lt;/p&gt;
&lt;p&gt;And thanks to my new observability baseline, I know exactly &lt;strong&gt;where to strike next&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13072/escaping-the-sync-trap-how-i-slashed-latency-by-10x-in-a-django-rust-api-gateway</guid>
<pubDate>Tue, 17 Mar 2026 17:16:00 +0000</pubDate>
</item>
<item>
<title>Exploring Microservices with Node.js</title>
<link>https://coderlegion.com/11728/exploring-microservices-with-node-js</link>
<description>&lt;p&gt;Dive into the microservices architecture using Node.js to create scalable applications. Learn practical strategies for implementation.&lt;/p&gt;
&lt;p&gt;Microservices architecture has gained traction for its scalability and flexibility. When it comes to Node.js, it shines due to its non-blocking I/O and event-driven model.&lt;/p&gt;
&lt;p&gt;To get started with microservices using Node.js, consider breaking your application into small independent services, each handling a specific task. For instance, if you're building an e-commerce platform, you could have separate services for user authentication, product listings, order management, etc.&lt;/p&gt;
&lt;h4 id=&quot;key-considerations&quot;&gt;Key Considerations&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service Communication&lt;/strong&gt;: Depending on your application needs, you might want to use REST APIs, gRPC, or message queues like RabbitMQ for service communication. Each has its pros and cons based on latency and throughput.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service Deployment&lt;/strong&gt;: Tools like Docker and Kubernetes can simplify the deployment of Node.js microservices. They allow you to manage instances and scale effortlessly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Management&lt;/strong&gt;: You can use either a shared database or separate databases for each service. While a shared database may simplify data consistency, it can become a bottleneck.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitoring and Logging&lt;/strong&gt;: Implement robust monitoring using tools like Prometheus or ELK Stack. By doing this, you ensure that each service is functioning optimally and can quickly identify issues.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;real-world-example&quot;&gt;Real-world Example&lt;/h4&gt;
&lt;p&gt;Take a look at companies like Netflix and Uber. They have successfully adopted microservices architecture with Node.js, allowing them to release updates independently and scale their applications depending on user demand.&lt;/p&gt;
&lt;h4 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;By embracing microservices with Node.js, you can build resilient and scalable applications. Remember, the key is to keep services as small and focused as possible, promoting an agile development cycle. Consider experimenting with a simple project to understand the nuances better, and soon you’ll see the benefits in your production environment.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11728/exploring-microservices-with-node-js</guid>
<pubDate>Tue, 17 Feb 2026 05:59:17 +0000</pubDate>
</item>
<item>
<title>Building Production-Ready Spring Boot Microservices: Lessons from Scaling to Millions</title>
<link>https://coderlegion.com/11476/building-production-ready-spring-boot-microservices-lessons-from-scaling-to-millions</link>
<description>&lt;h3 id=&quot;introduction-1&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Over the past several years working in e-commerce, I've architected and scaled microservices handling millions of customer requests daily, including platforms processing over $100M in transactions. In this article, I'll share practical patterns for building Spring Boot microservices that can handle production scale.&lt;/p&gt;
&lt;p&gt;This isn't theory—these are battle-tested approaches proven under real-world load.&lt;/p&gt;
&lt;h3 id=&quot;1-service-boundaries-and-domain-driven-design&quot;&gt;1. Service Boundaries and Domain-Driven Design&lt;/h3&gt;
&lt;p&gt;The biggest mistake teams make is creating microservices that are too granular or tightly coupled.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key principle:&lt;/strong&gt; Each service should own a complete business capability.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// ❌ BAD: Services that are too granular
CustomerNameService
CustomerAddressService

// ✅ GOOD: Services that own complete domains
CustomerProfileService // owns all customer identity/profile data
SubscriptionService    // owns subscriptions, benefits, rewards
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When building a subscription platform, a &lt;code&gt;SubscriptionService&lt;/code&gt; should own the complete domain: enrollment, benefits tracking, payment integration, and conversions. This prevents the &quot;distributed monolith&quot; where you call 10 services for one operation.&lt;/p&gt;
&lt;h3 id=&quot;2-handling-distributed-transactions-with-saga-pattern&quot;&gt;2. Handling Distributed Transactions with Saga Pattern&lt;/h3&gt;
&lt;p&gt;At scale, distributed transactions become your enemy. Here's the orchestration approach:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class SubscriptionEnrollmentOrchestrator {
    
    @Transactional
    public EnrollmentResult enrollMember(EnrollmentRequest request) {
        // Step 1: Validate customer (synchronous)
        CustomerProfile customer = customerClient.getCustomer(request.getCustomerId())
            .orElseThrow(() -&amp;gt; new CustomerNotFoundException());
            
        // Step 2: Process payment (synchronous with compensation)
        PaymentResult payment;
        try {
            payment = paymentClient.processPayment(request.getPaymentMethod());
        } catch (PaymentException e) {
            throw new EnrollmentFailedException(&quot;Payment failed&quot;, e);
        }
        
        // Step 3: Create subscription (local transaction)
        Subscription subscription;
        try {
            subscription = createSubscription(customer, payment);
            subscriptionRepository.save(subscription);
        } catch (Exception e) {
            // Compensate payment
            paymentClient.refundPayment(payment.getTransactionId());
            throw new EnrollmentFailedException(&quot;Subscription creation failed&quot;, e);
        }
        
        // Step 4: Publish event for async operations
        eventPublisher.publish(new SubscriptionEnrolledEvent(subscription));
        
        return EnrollmentResult.success(subscription);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key lessons:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Make operations idempotent&lt;/strong&gt; - Include transaction IDs so retries don't cause duplicates&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compensate explicitly&lt;/strong&gt; - If step N fails, undo steps 1 through N-1&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separate critical path from eventual consistency&lt;/strong&gt; - Payment is synchronous, emails are async&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;3-strategic-caching-for-high-traffic-systems&quot;&gt;3. Strategic Caching for High-Traffic Systems&lt;/h3&gt;
&lt;p&gt;We reduced database load by 85% through strategic caching:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class SubscriptionService {
    
    private final LoadingCache&amp;lt;String, Optional&amp;lt;Subscription&amp;gt;&amp;gt; cache;
    
    public SubscriptionService(SubscriptionRepository repository) {
        this.cache = CacheBuilder.newBuilder()
            .maximumSize(10_000)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .recordStats()
            .build(CacheLoader.from(repository::findByCustomerId));
    }
    
    @Transactional
    public Subscription updateSubscription(String id, SubscriptionUpdate update) {
        Subscription updated = repository.findById(id)
            .map(s -&amp;gt; applyUpdate(s, update))
            .orElseThrow(() -&amp;gt; new SubscriptionNotFoundException(id));
            
        repository.save(updated);
        cache.invalidate(updated.getCustomerId()); // Invalidate on write
        
        return updated;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Caching layers:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Application-level (Guava/Caffeine) - 5-minute TTL for hot data&lt;/li&gt;
&lt;li&gt;Redis distributed cache - 15-minute TTL for cross-instance consistency&lt;/li&gt;
&lt;li&gt;Database with proper indexing&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;4-circuit-breakers-and-resilience&quot;&gt;4. Circuit Breakers and Resilience&lt;/h3&gt;
&lt;p&gt;When calling other services, assume they will fail:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class CustomerProfileClient {
    
    @CircuitBreaker(name = &quot;customer-profile&quot;, fallbackMethod = &quot;getCustomerFallback&quot;)
    @TimeLimiter(name = &quot;customer-profile&quot;)
    public CompletableFuture&amp;lt;CustomerProfile&amp;gt; getCustomer(String customerId) {
        return CompletableFuture.supplyAsync(() -&amp;gt; 
            restTemplate.getForObject(&quot;/api/customers/&quot; + customerId, CustomerProfile.class)
        );
    }
    
    private CompletableFuture&amp;lt;CustomerProfile&amp;gt; getCustomerFallback(String customerId, Exception ex) {
        log.warn(&quot;Customer service unavailable, using cached data&quot;);
        return CompletableFuture.completedFuture(
            customerCache.get(customerId)
                .orElseThrow(() -&amp;gt; new ServiceUnavailableException(&quot;No cached data&quot;, ex))
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Configuration:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;CircuitBreakerConfig.custom()
    .failureRateThreshold(50)          // Open if 50% fail
    .waitDurationInOpenState(Duration.ofSeconds(30))
    .slidingWindowSize(100)            // Consider last 100 calls
    .slowCallDurationThreshold(Duration.ofSeconds(2))
    .build();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;5-structured-logging-and-distributed-tracing&quot;&gt;5. Structured Logging and Distributed Tracing&lt;/h3&gt;
&lt;p&gt;You can't fix what you can't see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Slf4j
@RestController
public class SubscriptionController {
    
    @PostMapping(&quot;/subscriptions&quot;)
    public ResponseEntity&amp;lt;SubscriptionResponse&amp;gt; enrollMember(
            @RequestBody EnrollmentRequest request,
            @RequestHeader(&quot;X-Request-ID&quot;) String requestId) {
        
        MDC.put(&quot;requestId&quot;, requestId);
        MDC.put(&quot;customerId&quot;, request.getCustomerId());
        
        try {
            log.info(&quot;Starting enrollment&quot;, 
                kv(&quot;tier&quot;, request.getTier()),
                kv(&quot;paymentMethod&quot;, request.getPaymentMethod().getType()));
            
            Instant start = Instant.now();
            EnrollmentResult result = enrollmentService.enroll(request);
            
            log.info(&quot;Enrollment completed&quot;,
                kv(&quot;subscriptionId&quot;, result.getSubscriptionId()),
                kv(&quot;durationMs&quot;, Duration.between(start, Instant.now()).toMillis()));
            
            return ResponseEntity.ok(toResponse(result));
        } finally {
            MDC.clear();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Logging standards:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use structured key-value pairs, not string concatenation&lt;/li&gt;
&lt;li&gt;Track request IDs across service boundaries&lt;/li&gt;
&lt;li&gt;Include customer ID and operation type in every log&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;6-database-query-optimization&quot;&gt;6. Database Query Optimization&lt;/h3&gt;
&lt;p&gt;Slow queries kill scalability:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity
@Table(name = &quot;subscriptions&quot;, indexes = {
    @Index(name = &quot;idx_customer_id&quot;, columnList = &quot;customer_id&quot;),
    @Index(name = &quot;idx_tier_status&quot;, columnList = &quot;tier, status&quot;),
    @Index(name = &quot;idx_expires_at&quot;, columnList = &quot;expires_at&quot;)
})
public class Subscription {
    // Use @BatchSize to prevent N+1 queries
    @OneToMany(mappedBy = &quot;subscription&quot;, fetch = FetchType.LAZY)
    @BatchSize(size = 50)
    private List&amp;lt;Benefit&amp;gt; benefits;
}

@Repository
public interface SubscriptionRepository extends JpaRepository&amp;lt;Subscription, String&amp;gt; {
    
    // Custom query with fetch join to avoid N+1
    @Query(&quot;SELECT s FROM Subscription s LEFT JOIN FETCH s.benefits WHERE s.customerId = :customerId&quot;)
    Optional&amp;lt;Subscription&amp;gt; findByCustomerIdWithBenefits(@Param(&quot;customerId&quot;) String customerId);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Performance lessons:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Index everything you query by&lt;/li&gt;
&lt;li&gt;Use batch fetching to prevent N+1 queries&lt;/li&gt;
&lt;li&gt;Paginate large results&lt;/li&gt;
&lt;li&gt;Monitor and alert on queries &amp;gt;100ms&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;7-connection-pool-tuning&quot;&gt;7. Connection Pool Tuning&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spring:
  datasource:
    hikari:
      # Formula: (core_count * 2) + effective_spindle_count
      # For 4-core with SSD: (4 * 2) + 1 = 9
      maximum-pool-size: 10
      minimum-idle: 5
      connection-timeout: 3000
      validation-timeout: 1000
      leak-detection-threshold: 60000
      max-lifetime: 1800000  # 30 minutes
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;8-zero-downtime-deployments&quot;&gt;8. Zero-Downtime Deployments&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: subscription-service
spec:
  replicas: 6
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 3        # Add 50% extra during update
      maxUnavailable: 1  # Keep 5/6 running
  template:
    spec:
      containers:
      - name: subscription-service
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 10
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;9-metrics-that-matter&quot;&gt;9. Metrics That Matter&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class MetricsService {
    
    private final Counter enrollmentsCounter;
    private final Timer enrollmentDuration;
    
    public MetricsService(MeterRegistry registry) {
        this.enrollmentsCounter = Counter.builder(&quot;subscription.enrollments&quot;)
            .tag(&quot;service&quot;, &quot;subscription&quot;)
            .register(registry);
            
        this.enrollmentDuration = Timer.builder(&quot;subscription.enrollment.duration&quot;)
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(registry);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Monitor:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Business: Enrollments/hour, conversion rate, revenue&lt;/li&gt;
&lt;li&gt;Technical: P50/P95/P99 latency, error rate, throughput&lt;/li&gt;
&lt;li&gt;Infrastructure: CPU, memory, connection pool utilization&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;10-testing-strategy&quot;&gt;10. Testing Strategy&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Unit tests - fast, isolated
@Test
void shouldCalculateCorrectExpirationDate() {
    EnrollmentService service = new EnrollmentService(
        mock(CustomerClient.class),
        mock(PaymentClient.class),
        mock(SubscriptionRepository.class)
    );
    
    Instant enrolledAt = Instant.parse(&quot;2024-01-01T00:00:00Z&quot;);
    Instant expected = Instant.parse(&quot;2025-01-01T00:00:00Z&quot;);
    
    assertEquals(expected, service.calculateExpiration(enrolledAt, SubscriptionTier.ANNUAL));
}

// Integration tests - with real database
@SpringBootTest
@Testcontainers
class SubscriptionServiceIntegrationTest {
    
    @Container
    static PostgreSQLContainer&amp;lt;?&amp;gt; postgres = new PostgreSQLContainer&amp;lt;&amp;gt;(&quot;postgres:15&quot;);
    
    @Test
    void shouldPersistAndRetrieveSubscription() {
        EnrollmentResult result = service.enroll(request);
        Optional&amp;lt;Subscription&amp;gt; retrieved = service.getSubscription(result.getSubscriptionId());
        
        assertTrue(retrieved.isPresent());
        assertEquals(SubscriptionTier.BASIC, retrieved.get().getTier());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;key-takeaways-1&quot;&gt;Key Takeaways&lt;/h3&gt;
&lt;p&gt;After building and scaling microservices handling millions of requests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Design for failure&lt;/strong&gt; - Circuit breakers, timeouts, and fallbacks are not optional&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embrace eventual consistency&lt;/strong&gt; - Not everything needs to be synchronous&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observability is critical&lt;/strong&gt; - You can't improve what you can't measure&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache aggressively&lt;/strong&gt; - But invalidate intelligently&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test at every level&lt;/strong&gt; - Unit, integration, contract, and load tests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automate deployments&lt;/strong&gt; - Including rollbacks and scaling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance is a feature&lt;/strong&gt; - Optimize from day one&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;what-s-next--2&quot;&gt;What's Next?&lt;/h3&gt;
&lt;p&gt;In future posts, I'll dive deeper into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Advanced observability with distributed tracing&lt;/li&gt;
&lt;li&gt;Chaos engineering and resilience testing&lt;/li&gt;
&lt;li&gt;Cost optimization for cloud deployments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Questions or want to discuss these patterns?&lt;/strong&gt; Leave a comment below or connect with me on &lt;a rel=&quot;nofollow&quot; href=&quot;https://linkedin.com/in/vamsithatikonda&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #spring-boot #microservices #java #system-design #aws #architecture #scalability #production-engineering&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11476/building-production-ready-spring-boot-microservices-lessons-from-scaling-to-millions</guid>
<pubDate>Thu, 12 Feb 2026 05:38:38 +0000</pubDate>
</item>
<item>
<title>Alvor Bank Architecture (.NET + Angular)</title>
<link>https://coderlegion.com/8239/alvor-bank-architecture-net-angular</link>
<description>&lt;p&gt;Hey everyone &lt;/p&gt;
&lt;p&gt;I’ve been working on a full-stack digital banking platform called Alvor Bank, built with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.NET 10 (microservices + Clean Architecture)   &lt;/li&gt;
&lt;li&gt;Angular 21 (monorepo-style frontend) &lt;/li&gt;
&lt;li&gt;PostgreSQL, RabbitMQ, YARP API Gateway &lt;/li&gt;
&lt;li&gt;Docker &amp;amp; GitHub Actions CI/CD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To move beyond “hello world” tutorials, I put together a small architecture &amp;amp; quality roadmap for the system, and I’m sharing the slides here.&lt;/p&gt;
&lt;p&gt;In the slides you can see:&lt;br&gt;
-The high-level architecture diagram&lt;br&gt;
-VS solution structure and how the projects are organized&lt;br&gt;
-How I enforce test coverage as a quality gate&lt;br&gt;
-A glimpse of the CI/CD pipeline (build, test, lint, Docker)&lt;br&gt;
-The security/IAM setup (centralized auth, roles, tenants)&lt;br&gt;
-The final project scope for the platform&lt;/p&gt;
&lt;p&gt;I’m also documenting everything step-by-step in an early-access book where I build this system from scratch, chapter by chapter:&lt;/p&gt;
&lt;p&gt; Modern Full-Stack Banking Microservices (in progress on Leanpub)&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://leanpub.com/fullstack-banking-microservices&quot;&gt;https://leanpub.com/fullstack-banking-microservices&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I’d really like to hear from other engineers here:&lt;/p&gt;
&lt;p&gt;How do you enforce quality gates (tests, coverage, linting, security) in your own projects?&lt;/p&gt;
&lt;p&gt;Happy to discuss architecture choices, trade-offs, or anything in the slides.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m not building another TODO app. I’m building a production-grade digital bank. Here's what that actually looks like in code&lt;/strong&gt;&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=649066434027511383&quot; alt=&quot;01_The_Hook&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;System overview: Microservices, event-driven notifications, and full IAM&lt;/strong&gt;&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=13534577123387403778&quot; alt=&quot;02_The_Architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real project structure: Clean Architecture layers, separate service projects, shared building blocks.&lt;/strong&gt;&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=12843825970440323757&quot; alt=&quot;03_ The_Code Structure&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We treat tests as a priority, not an afterthought. Coverage gates in CI ensure quality&lt;/strong&gt;&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11531434522214919027&quot; alt=&quot;04_Test_Coverage_Report&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Everything ships through CI/CD from day one. Green builds and enforced quality thresholds&lt;/strong&gt;&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=13063286760248210710&quot; alt=&quot;05_ci_cd_pipeline&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Coverage report test on CI/CD from Gih=thub Actions&lt;/strong&gt;&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=8310657715480859498&quot; alt=&quot;06_ci_cd_test_coverage&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is from my &lt;strong&gt;book Modern Banking Microservices with Clean Architecture, DDD, TDD, .NET 10, and Angular 21 (nx Monorepo)&lt;/strong&gt;. Go beyond tutorials. Build systems that matter. Link:   &lt;a rel=&quot;nofollow&quot; href=&quot;https://leanpub.com/fullstack-banking-microservices&quot;&gt;1&lt;/a&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://leanpub.com/fullstack-banking-microservices&quot;&gt;https://leanpub.com/fullstack-banking-microservices&lt;/a&gt;.&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=3186225544660607452&quot; alt=&quot;07_The_Call_to_Action&quot;&gt;&lt;/p&gt;
</description>
<category>Books</category>
<guid isPermaLink="true">https://coderlegion.com/8239/alvor-bank-architecture-net-angular</guid>
<pubDate>Sat, 06 Dec 2025 21:02:56 +0000</pubDate>
</item>
<item>
<title>Enhanced Paperless-NGX with Paddle OCR + LLM Pipeline</title>
<link>https://coderlegion.com/6107/enhanced-paperless-ngx-with-paddle-ocr-llm-pipeline</link>
<description>&lt;h3 id=&quot;building-a-private-ai-document-pipeline-with-paperless-paddleocr-and-llms&quot;&gt;Building a Private AI Document Pipeline with Paperless, PaddleOCR, and LLMs&lt;/h3&gt;
&lt;p&gt;So this past week I hacked together a little side project to smarten up my &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/paperless-ngx/paperless-ngx&quot;&gt;Paperless-ngx&lt;/a&gt; setup — you know, that self-hosted document management system that eats PDFs and makes them searchable.&lt;/p&gt;
&lt;p&gt;Now, Paperless-ngx is solid, don’t get me wrong. But it uses Tesseract for OCR, and honestly... Tesseract is not optimal for anything that's not clean text.&lt;/p&gt;
&lt;p&gt;So this just evolved out of need to improve paperless-ngx's OCR capability and to properly classify documents and extract tags. titles and summaries for documents.&lt;/p&gt;
&lt;p&gt;This blog is a walkthrough of what I built — what worked, what didn’t, and how it turned into a pretty neat little pipeline with its own microservices. Hope it gives you some ideas.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-was-going-for&quot;&gt;What I Was Going For&lt;/h3&gt;
&lt;p&gt;I wanted a system that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;strong&gt;PaddleOCR&lt;/strong&gt; instead of Tesseract for better OCR output&lt;/li&gt;
&lt;li&gt;Runs a &lt;strong&gt;local LLM&lt;/strong&gt; using &lt;a rel=&quot;nofollow&quot; href=&quot;https://ollama.com&quot;&gt;Ollama&lt;/a&gt; to:&lt;ul&gt;
&lt;li&gt;Suggest a smart document title&lt;/li&gt;
&lt;li&gt;Classify the document into a type (invoice, id, tax, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pushes that back to Paperless so the doc is nicely searchable and tagged&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything stays local/private. No exposure to external LLMs. Just Python, containers, compose to stitch everything together.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-it-all-works-now-&quot;&gt;How It All Works (Now)&lt;/h3&gt;
&lt;p&gt;After a few iterations, I ended up with a clean microservice setup with each part doing its job:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;paperless-ngx&lt;/code&gt;: The main document management system (already amazing)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ollama&lt;/code&gt;: Runs a local LLM like Mistral (or phi3 or any configurable in env), no cloud stuff&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ocr-service&lt;/code&gt;: FastAPI service that runs PaddleOCR&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;pipeline&lt;/code&gt;: Python CLI that connects all the dots — downloads doc from Paperless, sends it to OCR and LLM, then updates Paperless with the smart results&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That way, each service does one thing and does it well or can be enhanced in isolation. They all run in Docker, talk to each other over the same network, and make one smooth, local AI document workflow.&lt;/p&gt;
&lt;p&gt;This leverages &lt;code&gt;paperless-ngx&lt;/code&gt;'s extensive features and augments it with better OCR and LLM classification capabilities.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-flow-looks-like-this-&quot;&gt;The Flow Looks Like This:&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt; Paperless stores docs in its DB
⬇️
 I run my pipeline CLI: `docker compose run pipeline 42`
⬇️
 pipeline downloads the doc via Paperless API (by ID)
⬇️
 Sends it to the OCR microservice over HTTP
⬇️
 Gets back clean OCR’d text
⬇️
 Sends text to Ollama (LLM) to generate:
    - title
    - document type
⬇️
 Updates Paperless document via PATCH
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;everything-runs-in-docker&quot;&gt;Everything Runs in Docker&lt;/h3&gt;
&lt;p&gt;Here's the final list of containers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;paperless → standard Paperless-ngx&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;redis → required by Paperless&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ollama → runs local LLMs like Mistral&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ocr-service → FastAPI + PaddleOCR&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;pipeline → command-line microservice that ties it all together&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;architecture-diagram&quot;&gt;Architecture Diagram&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;                   ┌────────────────────────┐
                   │  Paperless-ngx (UI)  │
                   └────────────┬───────────┘
                                │
                    [User notes Document ID]
                                │
                   ┌────────────▼────────────┐
                   │    Pipeline Service    │
                   │ (LLM &amp;amp; Orchestration)   │
                   └────────────┬────────────┘
                                │
           ┌────────────────────┼────────────────────┐
           │                    │                    │
           ▼                    ▼                    ▼
  ┌────────────────┐   ┌────────────────┐   ┌────────────────────┐
  │ Downloads PDF  │   │ Sends to OCR   │   │ Sends OCR text to  │
  │ via Paperless  │   │ microservice   │   │ Ollama LLM (Mistral)│
  └────────────────┘   └────────────────┘   └────────────────────┘
                                                │
                     ◀────────────┬─────────────┘
                                  ▼
                        Title + Type Prediction
                                  │
                        PATCH back to Paperless
                       (update metadata + text)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;what-gave-me-trouble&quot;&gt;What Gave Me Trouble&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Paperless consumes files from consume/ automatically and moves it — I had to work around that by working only with doc IDs via API as at this time I was more focused on adding OCR/AI features. This one is high on my TODO list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PaddleOCR kept re-downloading models — made sure models are cached.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;folder-structure&quot;&gt;Folder Structure&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;├── docker-compose.yml
├── __init__.py
├── model-cache
├── ocr_service
│   ├── app
│   │   ├── __init__.py
│   │   ├── main.py
│   │   ├── ocr_config.yaml
│   │   └── ocr_engine.py
│   ├── Dockerfile
│   └── requirements.txt
├── paperless-data
│   ├── consume
│   ├── data
│   │   ├── db.sqlite3
│   │   ├── index
│   │   │   ├── _MAIN_63.toc
│   │   │   ├── MAIN_9v88o8vye3gbqub1.seg
│   │   │   ├── MAIN_ui8jrpftrvauh4n1.seg
│   │   │   ├── MAIN_wceagdbh71brm5wg.seg
│   │   │   └── MAIN_WRITELOCK
│   │   ├── log
│   │   │   └── celery.log.1
│   │   └── migration_lock
│   └── media
│       ├── documents
│       │   ├── archive
│       │   │   ├── 0000009.pdf
│       │   │   └── 0000016.pdf
│       │   ├── originals
│       │   │   ├── 0000009.jpg
│       │   │   └── 0000016.pdf
│       │   └── thumbnails
│       │       ├── 0000009.webp
│       │       └── 0000016.webp
│       └── media.lock
├── pipeline_service
│   ├── app
│   │   ├── api_client.py
│   │   ├── __init__.py
│   │   ├── llm_processor.py
│   │   ├── main.py
│   │   └── watcher.py
│   ├── Dockerfile
│   ├── __init__.py
│   ├── logger.py
│   ├── prompts
│   │   └── classify_title.txt
│   ├── requirements.txt
│   └── test.py
└── README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;-what-s-next-&quot;&gt;✅ What's Next?&lt;/h3&gt;
&lt;p&gt;I might:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Run the pipeline automatically when a new doc lands&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add authentication for OCR and pipeline service (utilizing &lt;code&gt;paperless-ngx&lt;/code&gt;'s token auth?)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improve performance of OCR service, perhaps using other language (Go, Rust)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add document summarization via LLM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extract metadata like amount, date, sender&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hook into Paperless tags and correspondents&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h3&gt;
&lt;p&gt;If you need a document management system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;That ingests all kinds off docs PDFs, images etc.&lt;/li&gt;
&lt;li&gt;That reliably extracts text from varied kind of docs.&lt;/li&gt;
&lt;li&gt;That classifies, tags and summarizes docs using LLM.&lt;/li&gt;
&lt;li&gt;That keeps stuff private - no exposure to external LLMs.&lt;/li&gt;
&lt;li&gt;It comes with features loaded from &lt;code&gt;paperless-ngx&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then this might be an appealing setup.&lt;/p&gt;
&lt;p&gt;It’s Python all the way down. No rocket science — just containers, OCR, and a private LLM.&lt;/p&gt;
&lt;p&gt;Questions or inputs are welcome.&lt;/p&gt;
</description>
<category>Backend Dev</category>
<guid isPermaLink="true">https://coderlegion.com/6107/enhanced-paperless-ngx-with-paddle-ocr-llm-pipeline</guid>
<pubDate>Mon, 06 Oct 2025 09:15:37 +0000</pubDate>
</item>
<item>
<title>Saga Pattern — an Introduction</title>
<link>https://coderlegion.com/4763/saga-pattern-an-introduction</link>
<description>&lt;p&gt;In the world of microservices and distributed systems, managing data consistency across multiple services presents unique challenges. Traditional database transactions with their ACID guarantees work beautifully within a single database, but they fall short when your business logic spans multiple services, each with its own database. Enter the Saga pattern—a powerful approach to handling distributed transactions that has become essential in modern microservices architectures.&lt;/p&gt;
&lt;p&gt;You can read the blog or &lt;strong&gt;read the&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf&quot;&gt;research paper&lt;/a&gt;. I recommend the latter.&lt;/p&gt;
&lt;p&gt;Sample projects:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/online-store-saga-choreography&quot;&gt;Online Store Saga Choreography Example&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/hotel-booking-saga-orchestration&quot;&gt;Hotel Booking Saga Orchestration Example&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-is-the-saga-pattern-&quot;&gt;What is the Saga Pattern?&lt;/h3&gt;
&lt;p&gt;The Saga pattern, first introduced by Hector Garcia-Molina and Kenneth Salem in their 1987 research paper, provides a way to manage long-running transactions that span multiple services in a distributed system. Rather than treating the entire business process as a single atomic transaction, a saga breaks it down into a series of smaller, independent transactions that can be coordinated across services.&lt;/p&gt;
&lt;p&gt;Think of booking a vacation package that involves reserving a flight, hotel, and rental car. In a monolithic system, you might wrap all these operations in a single database transaction. With microservices, each service (Flight Service, Hotel Service, Car Rental Service) manages its own data independently. The Saga pattern allows you to coordinate these separate operations while maintaining the ability to handle failures gracefully.&lt;/p&gt;
&lt;p&gt;At its core, a saga consists of a sequence of transactions T₁, T₂, ..., Tₙ, where each transaction has a corresponding compensating transaction C₁, C₂, ..., Cₙ. If any transaction fails, the saga executes the compensating transactions in reverse order to undo the work already completed, ensuring the system remains in a consistent state.&lt;/p&gt;
&lt;h3 id=&quot;problems-it-solves-and-consistency-trade-offs&quot;&gt;Problems it Solves and Consistency Trade-offs&lt;/h3&gt;
&lt;p&gt;The Saga pattern addresses several critical challenges in distributed systems:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Distributed Data Management&lt;/strong&gt;: In microservices architectures, services are designed to be autonomous, each owning its data. Traditional two-phase commit protocols can create tight coupling and availability issues across services. Sagas enable coordination without sacrificing service autonomy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Long-Running Processes&lt;/strong&gt;: Business processes often involve multiple steps that may take minutes, hours, or even days to complete. Holding database locks for such extended periods is impractical and can severely impact system performance. Sagas allow these processes to progress incrementally without blocking resources.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Failure Handling&lt;/strong&gt;: In distributed systems, failures are inevitable. Network partitions, service outages, and timeouts are part of the reality. Sagas provide a structured approach to handle these failures through compensation, rather than simply rolling back and starting over.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eventual Consistency Trade-off&lt;/strong&gt;: The Saga pattern embraces eventual consistency over immediate consistency. This means that at any given moment, the system might be in an intermediate state, but it will eventually reach a consistent state once the saga completes or compensates. This trade-off is acceptable—and often preferable—in many business scenarios where absolute consistency isn't critical, but availability and resilience are paramount.&lt;/p&gt;
&lt;p&gt;Unlike strict ACID transactions that provide immediate consistency but can be brittle in distributed environments, sagas offer a pragmatic approach that acknowledges the realities of distributed systems. The business logic determines whether eventual consistency is acceptable, and in most real-world scenarios involving multiple services, it is.&lt;/p&gt;
&lt;h3 id=&quot;types-of-saga-patterns&quot;&gt;Types of Saga Patterns&lt;/h3&gt;
&lt;p&gt;The Saga pattern can be implemented using two primary approaches, each with distinct characteristics and use cases.&lt;/p&gt;
&lt;h4 id=&quot;choreography-based-sagas&quot;&gt;Choreography-Based Sagas&lt;/h4&gt;
&lt;p&gt;In choreography-based sagas, services coordinate themselves through events without a central coordinator. Each service listens for events, performs its part of the transaction, and publishes events for other services to consume.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;: When a user places an order, the Order Service creates the order and publishes an &quot;OrderCreated&quot; event. The Payment Service listens for this event, processes payment, and publishes a &quot;PaymentProcessed&quot; event. The Inventory Service then reserves items and publishes an &quot;ItemsReserved&quot; event, and so on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decentralized control promotes service autonomy&lt;/li&gt;
&lt;li&gt;No single point of failure from a coordinator perspective&lt;/li&gt;
&lt;li&gt;Natural fit for event-driven architectures&lt;/li&gt;
&lt;li&gt;Services remain loosely coupled&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Complex to track and debug the overall flow&lt;/li&gt;
&lt;li&gt;Difficult to understand the complete business process from code&lt;/li&gt;
&lt;li&gt;Challenging to handle circular dependencies&lt;/li&gt;
&lt;li&gt;Error handling can become distributed and complex&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: Choose choreography when you have a relatively simple saga with clear, linear flow and when you want to maximize service independence. It works well for scenarios where the business process is stable and unlikely to change frequently.&lt;/p&gt;
&lt;p&gt;For a practical example, check out this &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/online-store-saga-choreography&quot;&gt;choreography-based online store implementation&lt;/a&gt; that demonstrates how services coordinate through events to handle order processing.&lt;/p&gt;
&lt;h4 id=&quot;orchestration-based-sagas&quot;&gt;Orchestration-Based Sagas&lt;/h4&gt;
&lt;p&gt;In orchestration-based sagas, a central orchestrator (saga manager) controls the execution flow, explicitly calling services and managing the overall transaction state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;: A Saga Orchestrator receives a request to start a saga, then sequentially calls each service based on predefined logic. It maintains the saga's state and handles both success and failure scenarios by invoking appropriate compensating actions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clear, centralized control flow that's easy to understand and debug&lt;/li&gt;
&lt;li&gt;Explicit state management makes monitoring and troubleshooting straightforward&lt;/li&gt;
&lt;li&gt;Easier to implement complex routing logic and conditional flows&lt;/li&gt;
&lt;li&gt;Better support for timeout handling and retry mechanisms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Central orchestrator can become a bottleneck or single point of failure&lt;/li&gt;
&lt;li&gt;Orchestrator needs to know about all participating services&lt;/li&gt;
&lt;li&gt;Can lead to more coupled architecture&lt;/li&gt;
&lt;li&gt;Additional infrastructure component to maintain&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: Opt for orchestration when you have complex business flows with conditional logic, when you need clear visibility into the saga state, or when you're dealing with frequently changing business requirements that benefit from centralized control.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/hotel-booking-saga-orchestration&quot;&gt;hotel booking saga orchestration project&lt;/a&gt; provides a comprehensive example of how to implement orchestration-based sagas with proper state management and compensation handling.&lt;/p&gt;
&lt;h3 id=&quot;comparisons-with-other-patterns&quot;&gt;Comparisons with Other Patterns&lt;/h3&gt;
&lt;p&gt;Understanding how the Saga pattern compares to other consistency approaches helps clarify when to use each.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Saga vs. Two-Phase Commit (2PC)&lt;/strong&gt;: Two-Phase Commit provides strong consistency through a prepare-commit protocol but comes with significant drawbacks in distributed systems. It's blocking (services must wait for coordinator decisions), has poor fault tolerance (coordinator failure blocks everything), and doesn't scale well across networks with high latency. Sagas, in contrast, are non-blocking, more fault-tolerant, and better suited for loosely coupled microservices, though they provide only eventual consistency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Saga vs. Event Sourcing&lt;/strong&gt;: While both patterns work well in event-driven systems, they serve different purposes. Event sourcing focuses on storing state changes as events and rebuilding state from these events. Sagas focus on coordinating multi-service transactions. They complement each other well—you can implement sagas in an event-sourced system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Saga vs. Distributed Transactions&lt;/strong&gt;: Traditional distributed transactions aim for immediate consistency across all resources but are complex to implement correctly and perform poorly at scale. Sagas acknowledge that immediate consistency isn't always necessary and provide a simpler, more resilient alternative for most business scenarios.&lt;/p&gt;
&lt;p&gt;The practical advantage of sagas lies in their alignment with microservices principles: they maintain service autonomy, provide better availability characteristics, and offer a more pragmatic approach to consistency in distributed systems.&lt;/p&gt;
&lt;h3 id=&quot;implementation-in-practice-with-spring-boot&quot;&gt;Implementation in Practice with Spring Boot&lt;/h3&gt;
&lt;p&gt;Spring Boot provides excellent support for implementing saga patterns through various approaches. The most common implementation leverages Spring's event handling capabilities and message queues.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;choreography-based sagas&lt;/strong&gt;, developers typically use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Events for intra-service communication&lt;/li&gt;
&lt;li&gt;Message brokers (RabbitMQ, Apache Kafka) for inter-service events&lt;/li&gt;
&lt;li&gt;Spring Boot Actuator for monitoring saga progress&lt;/li&gt;
&lt;li&gt;Custom event handlers that implement both forward and compensating actions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For &lt;strong&gt;orchestration-based sagas&lt;/strong&gt;, the implementation often includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A dedicated Saga Orchestrator service built with Spring Boot&lt;/li&gt;
&lt;li&gt;Spring State Machine for managing saga states and transitions&lt;/li&gt;
&lt;li&gt;RestTemplate or WebClient for service-to-service communication&lt;/li&gt;
&lt;li&gt;Scheduled tasks for handling timeouts and retries&lt;/li&gt;
&lt;li&gt;Database persistence for saga state management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A typical Spring Boot saga implementation involves creating:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Saga Events&lt;/strong&gt;: Domain events that represent saga steps&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event Handlers&lt;/strong&gt;: Methods annotated with &lt;code&gt;@EventListener&lt;/code&gt; that process saga events&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compensation Logic&lt;/strong&gt;: Corresponding handlers for rollback operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State Management&lt;/strong&gt;: Tracking saga progress and current state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;: Timeout management and retry mechanisms&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The referenced sample projects demonstrate these concepts in action, showing how to structure your code, handle failures, and implement proper monitoring for production-ready saga implementations.&lt;/p&gt;
&lt;h3 id=&quot;common-challenges-and-testing-considerations&quot;&gt;Common Challenges and Testing Considerations&lt;/h3&gt;
&lt;p&gt;Implementing sagas in production environments presents several challenges that require careful consideration and planning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timeout Management&lt;/strong&gt;: Services in a saga may become temporarily unavailable or respond slowly. Implementing appropriate timeout strategies is crucial—too short, and you'll have false failures; too long, and failed sagas will tie up resources. Design your timeouts based on realistic service response times and implement exponential backoff for retries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compensating Transaction Complexity&lt;/strong&gt;: Not all operations can be easily compensated. Sending an email notification, for example, can't be &quot;unsent.&quot; Design your sagas to minimize non-compensatable actions, or implement semantic compensation (like sending an apology email). Sometimes, the compensation is more complex than the original transaction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Idempotency&lt;/strong&gt;: Saga steps may be retried due to network issues or timeouts, so all operations must be idempotent. This means that executing the same operation multiple times should have the same effect as executing it once. Implement proper idempotency keys and state checking to handle duplicate requests gracefully.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Partial Failure Scenarios&lt;/strong&gt;: The most challenging aspect of sagas is handling scenarios where some steps succeed while others fail, potentially leaving the system in an intermediate state. Design your business processes to be resilient to these intermediate states, and ensure your UI and downstream systems can handle eventual consistency appropriately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Testing Saga Flows&lt;/strong&gt;: Testing distributed sagas requires sophisticated approaches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unit Testing&lt;/strong&gt;: Test individual saga steps and their compensations in isolation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration Testing&lt;/strong&gt;: Use tools like TestContainers to test saga flows with real message brokers and databases&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chaos Testing&lt;/strong&gt;: Deliberately introduce failures at different points to verify compensation logic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;End-to-End Testing&lt;/strong&gt;: Test complete saga flows in staging environments that mirror production&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Monitoring and Observability&lt;/strong&gt;: Implement comprehensive logging and monitoring for saga execution. Track saga instances, their current state, execution times, and failure rates. Tools like distributed tracing can help you follow saga execution across multiple services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Design Recommendations&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep saga steps as small and focused as possible&lt;/li&gt;
&lt;li&gt;Design for failure from the beginning—assume every step can fail&lt;/li&gt;
&lt;li&gt;Implement proper dead letter queues for handling poison messages&lt;/li&gt;
&lt;li&gt;Use correlation IDs to track saga instances across services&lt;/li&gt;
&lt;li&gt;Consider implementing saga timeouts at the business process level&lt;/li&gt;
&lt;li&gt;Plan for manual intervention in complex failure scenarios&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key to successful saga implementation is thorough testing and gradual rollout. Start with simple, linear sagas before moving to complex orchestration scenarios, and always have monitoring and alerting in place to catch issues early.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-2&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The Saga pattern represents a pragmatic approach to managing distributed transactions in microservices architectures. By embracing eventual consistency and providing structured failure handling through compensation, sagas enable developers to build resilient, scalable systems that can handle the complexities of distributed environments.&lt;/p&gt;
&lt;p&gt;Whether you choose choreography for simple, event-driven flows or orchestration for complex business processes, the key is to understand your specific requirements and constraints. The pattern's flexibility allows for various implementation approaches, from simple Spring Boot applications to sophisticated orchestration engines.&lt;/p&gt;
&lt;p&gt;As distributed systems continue to evolve, the Saga pattern remains a fundamental tool for managing complexity while maintaining the benefits of microservices architecture. Success with sagas comes from careful design, thorough testing, and a clear understanding of the consistency trade-offs that make modern distributed systems both scalable and resilient.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;references-1&quot;&gt;References&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Garcia-Molina, H., &amp;amp; Salem, K. (1987). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf&quot;&gt;Sagas&lt;/a&gt;. ACM SIGMOD Record.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/cloud-native-daily/microservices-patterns-part-04-saga-pattern-a7f85d8d4aa3&quot;&gt;Microservices Patterns: Saga Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/online-store-saga-choreography&quot;&gt;Online Store Saga Choreography Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rajkundalia/hotel-booking-saga-orchestration&quot;&gt;Hotel Booking Saga Orchestration Example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Backend Dev</category>
<guid isPermaLink="true">https://coderlegion.com/4763/saga-pattern-an-introduction</guid>
<pubDate>Sun, 17 Aug 2025 07:58:36 +0000</pubDate>
</item>
<item>
<title>DevLog 20250807: Microservice for Cloud Scheduling (and Running)</title>
<link>https://coderlegion.com/4500/devlog-20250807-microservice-for-cloud-scheduling-and-running</link>
<description>&lt;h3 id=&quot;motivation&quot;&gt;Motivation&lt;/h3&gt;
&lt;p&gt;When working with routine tasks, it's convenient to have some form of automatic scheduling. Windows has &lt;strong&gt;Task Scheduler&lt;/strong&gt;, Linux has &lt;code&gt;cron&lt;/code&gt;, and in AWS, you can use &lt;strong&gt;Lambda&lt;/strong&gt; with &lt;strong&gt;EventBridge&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Wouldn't it be nice if we could periodically pull data from the web, do some light processing, and then dump the results into a database - maybe update a website, trigger a CI/CD process, or send a few emails?&lt;/p&gt;
&lt;p&gt;I haven't used it, but I imagine that's what &lt;strong&gt;Zapier&lt;/strong&gt; is designed for. Azure/GitHub Actions are certainly handy for CI/CD. But what if we just want to quickly prototype something, ship it to a server, and forget about it?&lt;/p&gt;
&lt;p&gt;That's the vision, anyway.&lt;/p&gt;
&lt;h3 id=&quot;the-first-attempts&quot;&gt;The First Attempts&lt;/h3&gt;
&lt;p&gt;When I first started building services for cloud computing, I combined both &quot;trigger a run&quot; and &quot;schedule repeated tasks&quot; into a single service. That sort of worked - but it didn't feel elegant. &lt;strong&gt;Something was off.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even now, after deciding to completely separate the &quot;runner&quot; and &quot;scheduler&quot; into two distinct microservices, I can't fully articulate &lt;em&gt;why&lt;/em&gt; this approach feels better. It just does. Of course, this means maintaining three or four separate programs now (runner, runner service, scheduler, scheduler service) - but each one is responsible for a single task, and that somehow feels more reassuring.&lt;/p&gt;
&lt;h3 id=&quot;separation-of-concerns-amp-the-single-responsibility-principle&quot;&gt;Separation of Concerns &amp;amp; the Single Responsibility Principle&lt;/h3&gt;
&lt;p&gt;I asked ChatGPT for a rationale that aligned with my intuition, and here's what it gave me:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;What you're discovering is a classic case of &lt;strong&gt;separation of concerns&lt;/strong&gt; (and in the microservice world, the &quot;scheduler-worker&quot; or &quot;cron-vs-runner&quot; pattern), underpinned by the Single Responsibility Principle. Here's why splitting &quot;who decides when&quot; from &quot;what actually does the work&quot; just &lt;em&gt;feels&lt;/em&gt; better.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;1-single-responsibility-amp-cognitive-load&quot;&gt;1. Single Responsibility &amp;amp; Cognitive Load&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scheduler service&lt;/strong&gt; - its sole job is: &quot;At 8 AM on Tuesdays, kick off job X.&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runner service&lt;/strong&gt; - its sole job is: &quot;When told to run X, do the fetch/process/dump.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By assigning each service a clear, focused purpose, you reduce logic complexity in any single codebase. You don't need to sift through your fetch logic to understand date calculations, and you don't need to worry about HTTP clients inside your scheduler.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Feels better&lt;/strong&gt; because each code path is narrow, easy to reason about, and small enough to hold entirely in your head.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;2-loose-coupling-amp-replaceability&quot;&gt;2. Loose Coupling &amp;amp; Replaceability&lt;/h4&gt;
&lt;p&gt;When the scheduler and runner communicate through a simple interface (e.g., message queue, webhook, or HTTP call), either can be swapped out independently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Want to switch from cron-style fixed schedules to GitHub webhook events?&lt;/li&gt;
&lt;li&gt;Need to rewrite your worker in another language for better library support?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You only have to reimplement one microservice - not the whole system.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Feels better&lt;/strong&gt; because the impact of change is localized.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;3-independent-scaling-amp-reliability&quot;&gt;3. Independent Scaling &amp;amp; Reliability&lt;/h4&gt;
&lt;p&gt;Scheduling frequency and execution workload rarely match 1:1.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maybe you schedule jobs every minute, but each run only takes a few seconds.&lt;/li&gt;
&lt;li&gt;Or you only need one scheduler, but dozens of parallel workers to clear a backlog.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Separating the two allows for independent sizing, deployment, and monitoring.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Feels better&lt;/strong&gt; because you avoid resource contention (e.g., a busy worker delaying the next schedule) and can autoscale each component based on its own metrics.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;4-clear-boundaries-for-observability-amp-error-handling&quot;&gt;4. Clear Boundaries for Observability &amp;amp; Error Handling&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scheduler logs&lt;/strong&gt; should only show: &quot;Job X was triggered at time T.&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runner logs&lt;/strong&gt; should only show: &quot;Processing of job X succeeded/failed.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This allows for more targeted alerting and diagnostics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alert if a scheduled trigger &lt;em&gt;never&lt;/em&gt; reaches the runner.&lt;/li&gt;
&lt;li&gt;Alert if the runner fails more than N times, regardless of the schedule.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Feels better&lt;/strong&gt; because root cause analysis is straightforward - you know where to look first.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;5-pattern-analogues-amp-ecosystem-support&quot;&gt;5. Pattern Analogues &amp;amp; Ecosystem Support&lt;/h4&gt;
&lt;p&gt;This approach mirrors patterns built into major platforms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS CloudWatch Events (scheduler) → Lambda (worker)&lt;/li&gt;
&lt;li&gt;Kubernetes CronJob (scheduler) → Job pods (workers)&lt;/li&gt;
&lt;li&gt;Celery Beat (scheduler) → Celery workers (task executors)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of them separate the &quot;when&quot; from the &quot;what,&quot; and you're starting to feel the same advantages they offer at scale.&lt;/p&gt;
&lt;h4 id=&quot;in-summary&quot;&gt;In Summary&lt;/h4&gt;
&lt;p&gt;You're gravitating toward a &lt;strong&gt;Scheduler–Worker&lt;/strong&gt; (or &quot;Cron-as-a-Service&quot; + &quot;Stateless Runner&quot;) pattern because it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Reduces cognitive load&lt;/strong&gt; by narrowing each service's scope&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decouples&lt;/strong&gt; timing logic from business logic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enables independent scaling&lt;/strong&gt; and more precise reliability controls&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improves testability&lt;/strong&gt; (you can mock the scheduler in runner tests, and vice versa)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aligns&lt;/strong&gt; with proven cloud-native and messaging patterns&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That &quot;reassuring&quot; feeling is the comfort of a clean, modular architecture - you can see exactly &lt;em&gt;what&lt;/em&gt; does &lt;em&gt;what&lt;/em&gt;, and update or extend each part without unraveling the rest.&lt;/p&gt;
&lt;h3 id=&quot;the-analogy&quot;&gt;The Analogy&lt;/h3&gt;
&lt;p&gt;Here is the way I understand it: a rough analogy might be batch-processing vs programming - when chaining multiple operations, it's better to use shell scripting than to rely on spawning subprocesses directly in your source code.&lt;/p&gt;
</description>
<category>Backend Dev</category>
<guid isPermaLink="true">https://coderlegion.com/4500/devlog-20250807-microservice-for-cloud-scheduling-and-running</guid>
<pubDate>Fri, 08 Aug 2025 04:22:53 +0000</pubDate>
</item>
<item>
<title>Micro-services using event driven architecture</title>
<link>https://coderlegion.com/3693/micro-services-using-event-driven-architecture</link>
<description>&lt;h3 id=&quot;-from-rest-to-events-why-event-driven-microservices-are-the-upgrade-you-didn-t-know-you-needed&quot;&gt; From REST to Events: Why Event-Driven Microservices Are the Upgrade You Didn’t Know You Needed&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;Building scalable systems today isn’t about throwing more servers at&lt;br&gt;
the problem, it’s about rethinking how your services talk to each&lt;br&gt;
other.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you’ve ever been burned by tightly coupled REST calls in a microservice architecture, long chains of service-to-service calls, failed transactions halfway through, retry hell, you’re not alone.&lt;/p&gt;
&lt;p&gt;Been there. Debugged that.&lt;br&gt;
Let’s talk about the better way: event-driven architecture (EDA).&lt;/p&gt;
&lt;p&gt; Microservices Were Supposed to Fix Everything… Right?&lt;br&gt;
In theory, microservices let you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Deploy independently&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scale granularly&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ship faster&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But when your services are glued together with synchronous HTTP calls, you’re still in trouble:&lt;/p&gt;
&lt;p&gt;Service A goes down → Service B fails too&lt;/p&gt;
&lt;p&gt;Any latency → user feels it&lt;/p&gt;
&lt;p&gt;You spend more time writing retries and fallbacks than business logic.&lt;/p&gt;
&lt;p&gt;That’s when I realized: the real unlock isn’t microservices alone, it’s event-driven microservices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What Is Event-Driven Architecture?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In event-driven systems, services don’t call each other directly.&lt;br&gt;
They emit events, and other services listen and respond.&lt;/p&gt;
&lt;p&gt;  participant OrderService&lt;/p&gt;
&lt;p&gt;  participant Kafka&lt;/p&gt;
&lt;p&gt;  participant PaymentService&lt;/p&gt;
&lt;p&gt;  participant InventoryService&lt;/p&gt;
&lt;p&gt;  participant ShippingService&lt;/p&gt;
&lt;p&gt;  OrderService-&amp;gt;&amp;gt;Kafka: Publish &quot;OrderPlaced&quot;&lt;/p&gt;
&lt;p&gt;  Kafka-&amp;gt;&amp;gt;InventoryService: &quot;OrderPlaced&quot;&lt;/p&gt;
&lt;p&gt;  Kafka-&amp;gt;&amp;gt;PaymentService: &quot;OrderPlaced&quot;&lt;/p&gt;
&lt;p&gt;  InventoryService-&amp;gt;&amp;gt;Kafka: &quot;InventoryReserved&quot;&lt;/p&gt;
&lt;p&gt;  PaymentService-&amp;gt;&amp;gt;Kafka: &quot;PaymentConfirmed&quot;&lt;/p&gt;
&lt;p&gt;  Kafka-&amp;gt;&amp;gt;ShippingService: &quot;OrderReady&quot;&lt;/p&gt;
&lt;p&gt;This decoupling is game-changing.&lt;/p&gt;
&lt;p&gt; Why It Works Better&lt;/p&gt;
&lt;p&gt;✅ Loose coupling&lt;br&gt;
Services don’t know about each other.&lt;br&gt;
You can add or remove consumers anytime.&lt;/p&gt;
&lt;p&gt;✅ Resilience&lt;br&gt;
One service fails? Others still run.&lt;/p&gt;
&lt;p&gt;✅ Scalability&lt;br&gt;
Scale hot paths (e.g., payment, inventory) independently.&lt;/p&gt;
&lt;p&gt;✅ Auditability&lt;br&gt;
Events are logged, you get a trail of everything that happened.&lt;/p&gt;
&lt;p&gt;✅ Asynchronous by default&lt;br&gt;
Your users don’t wait while five services call each other like it’s a WhatsApp group chat.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Event Brokers: The Real MVPs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;These tools make EDA possible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Apache Kafka — High-throughput, persistent event log (my go-to)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RabbitMQ — Queue-based messaging with strong delivery guarantees&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS SNS/SQS — Easy serverless messaging on the cloud&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Others — NATS, Pulsar, Redis Streams&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pick one based on your throughput needs, latency tolerance, and operational skillset.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Real-World Flow: Order Processing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s say you place an order. Here's how the services react:&lt;/p&gt;
&lt;p&gt;OrderService emits OrderPlaced&lt;/p&gt;
&lt;p&gt;InventoryService listens, reserves items&lt;/p&gt;
&lt;p&gt;PaymentService listens, processes payment&lt;/p&gt;
&lt;p&gt;ShippingService listens, ships once inventory + payment are confirmed&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;No service talks to another directly. No coupling. Just clean,&lt;br&gt;
reactive design.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ It’s Not All Rainbows&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes, event-driven systems are powerful, but they’re not magic.&lt;/p&gt;
&lt;p&gt;Here’s what to watch out for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Eventual consistency: Not everything is instant.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Idempotency: Events can be duplicated. Handle it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Schema evolution: Plan for versioning your events.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Debugging: Distributed tracing is a must.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt; Final Thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you want systems that are:&lt;/p&gt;
&lt;p&gt;Scalable ✅&lt;/p&gt;
&lt;p&gt;Resilient ✅&lt;/p&gt;
&lt;p&gt;Loosely coupled ✅&lt;/p&gt;
&lt;p&gt;Cloud-ready ✅&lt;/p&gt;
&lt;p&gt;Then it’s time to look beyond REST.&lt;/p&gt;
&lt;p&gt;Start small. Maybe just one async event.&lt;br&gt;
Get a feel for it. Then go deeper.&lt;/p&gt;
&lt;p&gt;Because in modern backend systems, the question isn't:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Should I use microservices?” It's: “How are my services&lt;br&gt;
communicating?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;✍️ Written by Moses Daniel Kwaknat, backend engineer, API builder, and dev who’s finally making peace with distributed systems.&lt;/p&gt;
&lt;p&gt;Let’s talk microservices, message brokers, or how to escape REST hell &lt;/p&gt;
</description>
<category>Backend Dev</category>
<guid isPermaLink="true">https://coderlegion.com/3693/micro-services-using-event-driven-architecture</guid>
<pubDate>Tue, 01 Jul 2025 11:38:59 +0000</pubDate>
</item>
<item>
<title>Optimistic vs. Pessimistic Concurrency in EF Core (with Table Hints)</title>
<link>https://coderlegion.com/1720/optimistic-vs-pessimistic-concurrency-in-ef-core-with-table-hints</link>
<description>&lt;p&gt;Many experienced developers are familiar with database transactions but often overlook the key differences between optimistic and pessimistic concurrency control in Entity Framework Core (EF Core). &lt;/p&gt;
&lt;p&gt;Understanding both approaches and how EF Core handles concurrency tokens is essential for building scalable and safe data operations, especially in systems with high transaction rates.&lt;/p&gt;
&lt;p&gt;Concurrency control determines how multiple users or processes interact with the same data simultaneously. While optimistic concurrency is the default in EF Core, pessimistic concurrency is often misunderstood, misused, or completely ignored  even by seniors engineers.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations&quot;&gt; Related Read: Understanding and Using Table Hints in SQL Server&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can find the full code example at: &lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/EfCoreExamples/tree/Efcore-Concurrency&quot;&gt;https://github.com/stevsharp/EfCoreExamples/tree/Efcore-Concurrency&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; What Is a Concurrency Token?&lt;/p&gt;
&lt;p&gt;A concurrency token is a property that EF Core watches to detect whether a row has been modified since it was loaded. It's typically used in optimistic concurrency scenarios.&lt;/p&gt;
&lt;p&gt;EF Core uses this token during &lt;strong&gt;SaveChangesAsync()&lt;/strong&gt; to generate a WHERE clause that checks if the value is unchanged , if it has changed, a &lt;strong&gt;DbUpdateConcurrencyException&lt;/strong&gt; is thrown.&lt;/p&gt;
&lt;p&gt; How to Mark a Property as a Concurrency Token&lt;/p&gt;
&lt;p&gt;✅ Using [ConcurrencyCheck] Attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Product
{
    public int Id { get; set; }

    [ConcurrencyCheck]
    public int Stock { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Using [Timestamp] (for SQL Server rowversion column):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Timestamp]
public byte[] RowVersion { get; set; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Fluent Configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;modelBuilder.Entity&amp;lt;Product&amp;gt;()
    .Property(p =&amp;gt; p.Stock)
    .IsConcurrencyToken();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use any column as a concurrency token — even logical fields like LastModified, but &lt;strong&gt; (aka &lt;/strong&gt;) is the most reliable for SQL Server.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Optimistic Concurrency Control&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Optimistic concurrency assumes conflicts are rare, so it doesn’t lock any data during updates. Instead, it tracks changes using concurrency tokens and throws an exception if a conflict is detected during SaveChangesAsync().&lt;/p&gt;
&lt;p&gt;✅ When to Use&lt;/p&gt;
&lt;p&gt;Read-heavy apps with occasional writes (e.g., blog, e-commerce frontend)&lt;/p&gt;
&lt;p&gt;Systems where retries are acceptable&lt;/p&gt;
&lt;p&gt; How to Implement in EF Core with a Concurrency Token&lt;/p&gt;
&lt;p&gt;Entity:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Product
{
    public int Id { get; set; }
    public int Stock { get; set; }

    [Timestamp] // This is the concurrency token
    public byte[] RowVersion { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Save and Handle Concurrency:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;var product = await context.Products.FindAsync(1);&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (product.Stock &amp;gt; 0)
{
    product.Stock -= 1;

    try
    {
        await context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException ex)
    {
        var databaseValues = await ex.Entries.Single().GetDatabaseValuesAsync();
        ex.Entries.Single().OriginalValues.SetValues(databaseValues);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Pessimistic Concurrency Control&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pessimistic concurrency prevents conflicts altogether by locking the rows being read or modified. This ensures no one else can change the data until the transaction is complete.&lt;/p&gt;
&lt;p&gt;❗ Important: EF Core does not support table hints like `` in LINQ. These must be applied using raw SQL.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations&quot;&gt; Related Read: Understanding and Using Table Hints in SQL Server&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; Example: Locking Bank Account Row&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using (var context = new AppDbContext())
{
    using var transaction = await context.Database.BeginTransactionAsync();

    var account = await context.Accounts
        .FromSqlRaw(&quot;SELECT * FROM Accounts WITH (UPDLOCK, ROWLOCK) WHERE Id = {0}&quot;, 1)
        .AsTracking() // Ensures the entity is tracked for updates
        .FirstOrDefaultAsync();

    if (account != null)
    {
        account.Balance -= 100;

        await context.SaveChangesAsync();
    }

    await transaction.CommitAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;Why SaveChangesAsync() Alone Is Not Always Enough&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When using FromSqlRaw(), EF Core may not track the entity correctly for updates unless explicitly instructed.&lt;/p&gt;
&lt;p&gt;Using .AsTracking() ensures EF will detect changes.&lt;/p&gt;
&lt;p&gt;Without it, you would need to manually mark the entity as modified:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;context.Entry(account).State = EntityState.Modified;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; &lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=8344402934722218379&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;✅ Final Thoughts&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Concurrency tokens&lt;/strong&gt; (like RowVersion) play a central role in optimistic concurrency, enabling EF Core to detect conflicts without using locks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimistic concurrency&lt;/strong&gt; is ideal for most applications where data collisions are rare and performance is key.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pessimistic concurrency&lt;/strong&gt; is essential in financial or high-risk systems, where consistency outweighs performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EF Core does not support table hints like&lt;/strong&gt; &lt;code&gt;` in LINQ, but you can **leverage them via **&lt;/code&gt;`, with .AsTracking() to ensure updates are recognized.&lt;/p&gt;
&lt;p&gt;Many developers — even experienced ones — underestimate how concurrency should be managed in EF Core. The wrong approach can result in subtle bugs, lost data, or race conditions in production.&lt;/p&gt;
&lt;p&gt;  References &amp;amp; Further Reading&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations&quot;&gt;Microsoft Docs – Handling Concurrency Conflicts in EF Core&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations&quot;&gt;Microsoft Docs – Concurrency Tokens&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/stevsharp/understanding-and-using-table-hints-in-sql-server-4iia&quot;&gt;Dev.to – Understanding and Using Table Hints in SQL Server&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/querying/sql-queries?tabs=sqlserver&quot;&gt;Microsoft Docs – Raw SQL Queries in EF Core&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16&quot;&gt;SQL Server – Table Hints in Transact-SQL&lt;/a&gt;&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/1720/optimistic-vs-pessimistic-concurrency-in-ef-core-with-table-hints</guid>
<pubDate>Mon, 31 Mar 2025 10:01:25 +0000</pubDate>
</item>
<item>
<title>EF Core Bulk Updates and Deletes: Performance vs Change Tracking</title>
<link>https://coderlegion.com/1640/ef-core-bulk-updates-and-deletes-performance-vs-change-tracking</link>
<description>&lt;p&gt;Entity Framework Core 7 introduced two powerful methods for bulk operations: &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ExecuteUpdateAsync&lt;/strong&gt; and &lt;strong&gt;ExecuteDeleteAsync&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;These methods are designed for high-performance scenarios where you need to update or delete multiple records efficiently without loading them into memory. They work great, but there's one caveat: EF Core's Change Tracker is not aware of these changes.&lt;/p&gt;
&lt;p&gt;In this article, we explore how these methods work, why they bypass the &lt;strong&gt;Change Tracker&lt;/strong&gt;, and most importantly—how you can audit or track changes manually when using them.&lt;/p&gt;
&lt;p&gt;If you're not familiar with EF Core's Change Tracker, check out this &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/change-tracking/&quot;&gt;introductory guide on Change Tracking&lt;/a&gt; to understand how EF Core monitors entity states.&lt;/p&gt;
&lt;p&gt;Why Use ExecuteUpdateAsync and ExecuteDeleteAsync?&lt;/p&gt;
&lt;p&gt;These methods:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate direct SQL UPDATE and DELETE statements.&lt;/li&gt;
&lt;li&gt;Do not load entities into memory, improving performance.&lt;/li&gt;
&lt;li&gt;Bypass change tracking, which avoids unnecessary overhead.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example Usage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Bulk update
await context.Users
    .Where(u =&amp;gt; u.LastLogin &amp;lt; DateTime.UtcNow.AddYears(-1))
    .ExecuteUpdateAsync(setters =&amp;gt; setters.SetProperty(u =&amp;gt; u.IsInactive, true));

// Bulk delete
await context.Users
    .Where(u =&amp;gt; u.IsInactive)
    .ExecuteDeleteAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The Problem: No Change Tracking&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because EF Core does not load entities into memory during these operations, the Change Tracker never sees them. This means:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;No automatic change detection.&lt;/li&gt;
&lt;li&gt;No SaveChangesAsync() involvement.&lt;/li&gt;
&lt;li&gt;No triggers for audit or domain events.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, how do you track what was changed or deleted?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.Auditing Options for Bulk Operations&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Manual Pre-Query Before Execution&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Fetch affected entities before performing the batch operation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var affectedUsers = await context.Users
    .Where(u =&amp;gt; u.LastLogin &amp;lt; DateTime.UtcNow.AddYears(-1))
    .Select(u =&amp;gt; new { u.Id, u.Name, OldValue = u.IsInactive })
    .ToListAsync();

await context.Users
    .Where(u =&amp;gt; u.LastLogin &amp;lt; DateTime.UtcNow.AddYears(-1))
    .ExecuteUpdateAsync(setters =&amp;gt; setters.SetProperty(u =&amp;gt; u.IsInactive, true));

// Log changes manually
foreach (var user in affectedUsers)
{
    Console.WriteLine($&quot;User {user.Name} updated: IsInactive {user.OldValue} → true&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Precise control over what you log.&lt;/p&gt;
&lt;p&gt;Access to old values.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Requires an extra query.&lt;/p&gt;
&lt;p&gt;May impact performance for large datasets.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Audit Table Logging&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Save the affected data into an audit table before executing the bulk operation.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await context.AuditLogs.AddRangeAsync(affectedUsers.Select(user =&amp;gt; new AuditLog
{
    EntityName = &quot;User&quot;,
    EntityId = user.Id,
    ChangeType = &quot;Updated&quot;,
    ChangedAt = DateTime.UtcNow,
    Description = $&quot;User {user.Name} was updated: IsInactive {user.OldValue} → true&quot;
}));

await context.SaveChangesAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Database Triggers&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Define SQL triggers to capture changes directly in the database.&lt;/p&gt;
&lt;p&gt;Example :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TRIGGER trg_User_Delete
ON Users
AFTER DELETE
AS
BEGIN
    INSERT INTO AuditLogs (EntityName, EntityId, ChangeType, ChangedAt, Description)
    SELECT 'User', Id, 'Deleted', GETUTCDATE(), CONCAT('User ', Name, ' was deleted') FROM deleted;
END;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pros:&lt;/p&gt;
&lt;p&gt;Fully automated.&lt;/p&gt;
&lt;p&gt;Ensures auditing even if EF Core is bypassed.&lt;/p&gt;
&lt;p&gt;Cons:&lt;/p&gt;
&lt;p&gt;Less flexible.&lt;/p&gt;
&lt;p&gt;Harder to manage and version control.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;EF Core Interceptors (Advanced)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Capture executed SQL commands using DbCommandInterceptor.&lt;/p&gt;
&lt;p&gt;Limitation:&lt;/p&gt;
&lt;p&gt;You can log the SQL query but not the previous values of affected entities.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class AuditInterceptor : DbCommandInterceptor
{
    public override InterceptionResult&amp;lt;DbDataReader&amp;gt; ReaderExecuting(
        DbCommand command, CommandEventData eventData, InterceptionResult&amp;lt;DbDataReader&amp;gt; result)
    {
        Console.WriteLine($&quot;SQL Executed: {command.CommandText}&quot;);
        return base.ReaderExecuting(command, eventData, result);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Registering the Interceptor : &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ApplicationDbContext : DbContext
{
    public DbSet&amp;lt;User&amp;gt; Users { get; set; }
    public DbSet&amp;lt;AuditLog&amp;gt; AuditLogs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(&quot;YourConnectionStringHere&quot;);
        optionsBuilder.AddInterceptors(new AuditInterceptor());
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;EF Core's ExecuteUpdateAsync and ExecuteDeleteAsync offer incredible performance for bulk data operations but come at the cost of losing automatic change tracking.&lt;/p&gt;
&lt;p&gt;If you need to audit or track changes, the best approach is to manually query the affected data before execution or use a dedicated audit system such as audit tables or SQL triggers.&lt;/p&gt;
&lt;p&gt;Choose the method that balances performance and auditing needs for your application.&lt;/p&gt;
&lt;p&gt;What do you think? Do you prefer performance with manual tracking, or do you rely on EF Core's change tracking? Let us know your approach!&lt;/p&gt;
&lt;p&gt;You can find the full code example at: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/EfCoreExamples/tree/BulkUpdateAndDeletes&quot;&gt;https://github.com/stevsharp/EfCoreExamples/tree/BulkUpdateAndDeletes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;References&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#bulk-updates-and-deletes&quot;&gt;Microsoft Docs - EF Core 7: ExecuteUpdate and ExecuteDelete&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/change-tracking/&quot;&gt;Microsoft Docs - Change Tracking in EF Core&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors&quot;&gt;Microsoft Docs - Interceptors in EF Core&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/dotnet/efcore&quot;&gt;Community Discussion - EF Core GitHub Issue on Change Tracking and Bulk Operations&lt;/a&gt;&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/1640/ef-core-bulk-updates-and-deletes-performance-vs-change-tracking</guid>
<pubDate>Mon, 24 Mar 2025 20:25:26 +0000</pubDate>
</item>
<item>
<title>EF Core: Lazy Loading, Eager Loading, and Loading Data on Demand</title>
<link>https://coderlegion.com/1401/ef-core-lazy-loading-eager-loading-and-loading-data-on-demand</link>
<description>&lt;p&gt;When working with &lt;strong&gt;Entity Framework Core (EF Core),&lt;/strong&gt; there are different strategies for loading related data from the database.&lt;/p&gt;
&lt;p&gt;So, how do you load a collection or other &lt;strong&gt;related objects?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here are the main options:&lt;/p&gt;
&lt;p&gt;1️⃣ Eager Loading (Include)&lt;/p&gt;
&lt;p&gt;Loads related data immediately with the main entity using .Include().&lt;/p&gt;
&lt;p&gt;Example: Load an Order with its Customer&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var order = context.Orders
    .Include(o =&amp;gt; o.Customer) // Load related Customer
    .First();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example: Load a Customer with Their Orders&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var customer = context.Customers
    .Include(c =&amp;gt; c.Orders) // Load related Orders
    .First();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅Pros:&lt;/p&gt;
&lt;p&gt;✔ Avoids multiple database queries (N+1 problem)&lt;/p&gt;
&lt;p&gt;✔ Simple and easy to use&lt;/p&gt;
&lt;p&gt;❌ Cons:&lt;/p&gt;
&lt;p&gt;❌ Can load unnecessary data, increasing query size&lt;/p&gt;
&lt;p&gt;❌ Slower if you load too many relationships&lt;/p&gt;
&lt;p&gt;2️⃣ Explicit Loading (Entry().Reference() / Entry().Collection())&lt;/p&gt;
&lt;p&gt;Loads related data on demand (manually) using .Entry().&lt;/p&gt;
&lt;p&gt;Example: Load a Single Related Entity&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var order = context.Orders.First();

// Load related Customer manually
context.Entry(order).Reference(o =&amp;gt; o.Customer).Load();

Console.WriteLine(order.Customer.Name);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example: Load a Collection of Related Entities&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var customer = context.Customers.First();

// Load related Orders manually
context.Entry(customer).Collection(c =&amp;gt; c.Orders).Load();

Console.WriteLine($&quot;Orders count: {customer.Orders.Count}&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Pros:&lt;/p&gt;
&lt;p&gt;✔ More control over when data is loaded&lt;/p&gt;
&lt;p&gt;✔ Avoids unnecessary queries&lt;/p&gt;
&lt;p&gt;❌ Cons:&lt;/p&gt;
&lt;p&gt;❌ Requires additional queries&lt;/p&gt;
&lt;p&gt;❌ Must remember to load data explicitly&lt;/p&gt;
&lt;p&gt;3️⃣ Lazy Loading (Requires Proxies)&lt;/p&gt;
&lt;p&gt;Loads related data only when accessed.&lt;/p&gt;
&lt;p&gt;Install :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet add package Microsoft.EntityFrameworkCore.Proxies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Steps to Enable Lazy Loading&lt;/p&gt;
&lt;p&gt;Enable in DbContext:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected override void OnConfiguring(DbContextOptionsBuilder options)
{
    options.UseLazyLoadingProxies().UseSqlite(&quot;Data Source=mydb.db&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make navigation properties virtual&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public virtual Customer Customer { get; set; } = default!;
public virtual List&amp;lt;Order&amp;gt; Orders { get; set; } = new();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example Usage : &lt;/p&gt;
&lt;p&gt;var order = context.Orders.First();&lt;/p&gt;
&lt;p&gt;// Lazy loaded automatically when accessed&lt;/p&gt;
&lt;p&gt;Console.WriteLine(order.Customer.Name);&lt;/p&gt;
&lt;p&gt;✅ Pros:&lt;/p&gt;
&lt;p&gt;✔ Simplifies code (data is loaded automatically)&lt;/p&gt;
&lt;p&gt;✔ Works without modifying queries&lt;/p&gt;
&lt;p&gt;❌ Cons:&lt;/p&gt;
&lt;p&gt;❌ Requires proxies (extra package)&lt;/p&gt;
&lt;p&gt;❌ Can lead to multiple database hits (N+1 problem)&lt;/p&gt;
&lt;p&gt;4️⃣ Projection (Select with Anonymous Types)&lt;/p&gt;
&lt;p&gt;Loads only specific data instead of full entities.&lt;/p&gt;
&lt;p&gt;Example: Load Only Needed Fields&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var orders = context.Orders
    .Select(o =&amp;gt; new 
    { 
        o.OrderNumber, 
        CustomerName = o.Customer.Name 
    })
    .ToList();

foreach (var order in orders)
{
    Console.WriteLine($&quot;{order.OrderNumber} - {order.CustomerName}&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Pros:&lt;/p&gt;
&lt;p&gt;✔ Best for performance (loads only required fields)&lt;/p&gt;
&lt;p&gt;✔ Reduces unnecessary data transfer&lt;/p&gt;
&lt;p&gt;❌ Cons:&lt;/p&gt;
&lt;p&gt;❌ Cannot track entities (only works for read-only queries)&lt;/p&gt;
&lt;p&gt;Summary Table&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=18412293215726215640&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt; Best Practices&lt;br&gt;
Use Eager Loading (Include()) when related data is always needed.&lt;/p&gt;
&lt;p&gt;Use Explicit Loading if you want manual control.&lt;/p&gt;
&lt;p&gt;Use Lazy Loading only if necessary (it can cause performance issues).&lt;/p&gt;
&lt;p&gt;Use Projection (Select) when you need only some fields.&lt;/p&gt;
&lt;p&gt;Source Code&lt;/p&gt;
&lt;p&gt;You can find the complete source code for this tutorial at:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/stevsharp/EfCoreExamples&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here are some useful references to learn more about EF Core : &lt;/p&gt;
&lt;p&gt; Official Microsoft Documentation&lt;br&gt;
Entity Framework Core Loading Related Data&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/querying/related-data&quot;&gt;https://learn.microsoft.com/en-us/ef/core/querying/related-data&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Covers Eager Loading, Explicit Loading, and Lazy Loading with examples.&lt;br&gt;
EF Core: Configuring Lazy Loading&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy&quot;&gt;https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Explains how to enable Lazy Loading with proxies.&lt;br&gt;
EF Core: Using Include() for Eager Loading&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager&quot;&gt;https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Details how to use .Include() and .ThenInclude().&lt;br&gt;
EF Core Performance Considerations&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/en-us/ef/core/performance/&quot;&gt;https://learn.microsoft.com/en-us/ef/core/performance/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Best practices for optimizing queries and avoiding performance issues like N+1 queries.&lt;/p&gt;
&lt;p&gt; Community &amp;amp; Blog Articles&lt;br&gt;
EF Core Lazy Loading vs. Eager Loading - Which One Should You Use?&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.thinktecture.com/en/entity-framework-core/lazy-loading-vs-eager-loading/&quot;&gt;https://www.thinktecture.com/en/entity-framework-core/lazy-loading-vs-eager-loading/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A detailed comparison of when to use each strategy.&lt;br&gt;
EF Core Best Practices: Avoiding Performance Pitfalls&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.michalbialecki.com/2020/11/29/entity-framework-core-performance-best-practices/&quot;&gt;https://www.michalbialecki.com/2020/11/29/entity-framework-core-performance-best-practices/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Covers when to use .Include(), lazy loading, and projection.&lt;/p&gt;
</description>
<category>C#</category>
<guid isPermaLink="true">https://coderlegion.com/1401/ef-core-lazy-loading-eager-loading-and-loading-data-on-demand</guid>
<pubDate>Thu, 06 Mar 2025 16:31:00 +0000</pubDate>
</item>
<item>
<title>Microservices vs Monolithic Applications: Cybersecurity Considerations</title>
<link>https://coderlegion.com/1239/microservices-vs-monolithic-applications-cybersecurity-considerations</link>
<description>&lt;h2 id=&quot;microservices-vs-monolithic-applications-cybersecurity-considerations&quot;&gt;Microservices vs Monolithic Applications: Cybersecurity Considerations&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In today's digital landscape, choosing between microservices and monolithic architectures is a significant decision for organizations. Each architectural style presents unique challenges and benefits, especially regarding cybersecurity. Understanding the implications of both can help organizations better defend against threats.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;architecture-comparison-of-microservices-and-monolithic-applications&quot;&gt;Architecture Comparison of Microservices and Monolithic Applications&lt;/h4&gt;
&lt;p&gt;Microservices architecture breaks applications into smaller, independent services, while monolithic architecture consolidates everything into a single unit. This structural difference introduces distinct cybersecurity considerations for each model.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://strapi.cyprox.io/uploads/cyprox_ai_vulnerability_scanner_2_c0d367aed3.jpeg&quot; alt=&quot;cyprox-ai-vulnerability-scanner-2.jpeg&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;cybersecurity-best-practices-for-microservices&quot;&gt;Cybersecurity Best Practices for Microservices&lt;/h4&gt;
&lt;p&gt;When deploying microservices, it’s crucial to follow best practices to safeguard against vulnerabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;API Security&lt;/strong&gt;: Secure APIs that connect services. For more information on API security principles, refer to &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyprox.io/blog/Understanding-API-Security-with-OpenAPI-A-Security-Engineer-Perspective&quot;&gt;Understanding API Security with OpenAPI: A Security Engineer’s Perspective&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service Isolation&lt;/strong&gt;: Each service should operate independently to minimize threat impact.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Monitoring&lt;/strong&gt;: Implement robust logging and monitoring to detect potential security breaches in real time.&lt;br&gt;
&lt;img src=&quot;https://strapi.cyprox.io/uploads/cyprox_ai_vulnerability_scanner_1_c86ce56cd5.jpeg&quot; alt=&quot;cyprox-ai-vulnerability-scanner-1.jpeg&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;cybersecurity-best-practices-for-monolithic-applications&quot;&gt;Cybersecurity Best Practices for Monolithic Applications&lt;/h4&gt;
&lt;p&gt;Monolithic applications also require stringent security measures. Key practices include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Regular Patching&lt;/strong&gt;: Keeping libraries and frameworks updated to mitigate vulnerabilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Input Validation&lt;/strong&gt;: Ensuring all inputs into the application are validated and sanitized.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure Coding Practices&lt;/strong&gt;: Educating developers on secure coding to prevent common exploits.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For insights on common vulnerabilities, refer to &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyprox.io/blog/Understanding-the-OWASP-API-Security-Top-10&quot;&gt;Understanding the OWASP API Security Top 10&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://strapi.cyprox.io/uploads/cyprox_ai_vulnerability_scanner_6_fe0ce46a32.jpeg&quot; alt=&quot;cyprox-ai-vulnerability-scanner-6.jpeg&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;cybersecurity-risks-in-microservices-architecture&quot;&gt;Cybersecurity Risks in Microservices Architecture&lt;/h4&gt;
&lt;p&gt;While microservices offer flexibility and speed, they also introduce potential cybersecurity risks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Increased Attack Surface&lt;/strong&gt;: Each microservice presents a new potential entry point for attackers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex Inter-service Communication&lt;/strong&gt;: Monitoring and securing interactions between services can be challenging.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a comprehensive overview of vulnerabilities associated with microservices, see &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyprox.io/vulnerabilities&quot;&gt;Understanding Vulnerabilities&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://strapi.cyprox.io/uploads/cyprox_ai_vulnerability_scanner_3_98bed46d61.jpeg&quot; alt=&quot;cyprox-ai-vulnerability-scanner-3.jpeg&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;cybersecurity-risks-in-monolithic-architecture&quot;&gt;Cybersecurity Risks in Monolithic Architecture&lt;/h4&gt;
&lt;p&gt;Monolithic applications also face unique cybersecurity challenges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single Point of Failure&lt;/strong&gt;: A breach can compromise the entire application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Difficulties in Scalability&lt;/strong&gt;: Implementing security measures can be cumbersome in large monolithic systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For further context on security risks, consider reading &lt;a rel=&quot;nofollow&quot; href=&quot;https://cyprox.io/blog/The-Evolution-of-Internet-Traffic&quot;&gt;The Evolution of Internet Traffic&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://strapi.cyprox.io/uploads/cyprox_ai_vulnerability_scanner_8_4f81025e57.jpeg&quot; alt=&quot;cyprox-ai-vulnerability-scanner-8.jpeg&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;comparing-microservices-and-monolithic-applications-in-a-cybersecurity-context&quot;&gt;Comparing Microservices and Monolithic Applications in a Cybersecurity Context&lt;/h4&gt;
&lt;p&gt;In conclusion, microservices allow for more granular security controls, but they also require vigilance to manage inter-service vulnerabilities. In contrast, monolithic applications simplify security management at the potential cost of speed and agility. Organizations must evaluate their specific needs and the associated cybersecurity implications when choosing an architecture.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://strapi.cyprox.io/uploads/cyprox_ai_vulnerability_scanner_10aefc8cbd.jpeg&quot; alt=&quot;cyprox-ai-vulnerability-scanner.jpeg&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;resources-4&quot;&gt;Resources&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;API Security&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.owasp.org/index.php/REST_Security_Cheat_Sheet&quot;&gt;OWASP REST Security Cheat Sheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OWASP Microservices Security&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/www-project-microservices-security/&quot;&gt;OWASP Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NIST Cybersecurity Framework&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.nist.gov/cyberframework&quot;&gt;NIST.gov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MITRE ATT&amp;amp;CK Framework&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://attack.mitre.org/&quot;&gt;attack.mitre.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CISA Cyber Hygiene&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.cisa.gov/cyber-hygiene&quot;&gt;cisa.gov/cyber-hygiene&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OWASP Top Ten Vulnerabilities&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/www-project-top-ten/&quot;&gt;owasp.org&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
<category>Cybersecurity</category>
<guid isPermaLink="true">https://coderlegion.com/1239/microservices-vs-monolithic-applications-cybersecurity-considerations</guid>
<pubDate>Tue, 25 Feb 2025 08:03:27 +0000</pubDate>
</item>
<item>
<title>Mastering Amazon S3 Replication: Same-Region vs. Cross-Region Replication -</title>
<link>https://coderlegion.com/1033/mastering-amazon-s3-replication-same-region-vs-cross-region-replication</link>
<description>&lt;p&gt;Amazon Simple Storage Service (S3) is a powerhouse for cloud storage, offering unmatched scalability, durability, and security. Among its many features, S3 Replication plays a vital role in ensuring data availability, resilience, and compliance. AWS provides two types of replication mechanisms: Same-Region Replication (SRR) and Cross-Region Replication (CRR). While both serve to replicate objects, they cater to different business needs. Let’s dive deep into their differences, use cases, and best practices.&lt;/p&gt;
&lt;h2 id=&quot;-what-is-amazon-s3-replication-&quot;&gt;&lt;strong&gt; What is Amazon S3 Replication?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;S3 Replication is an AWS feature that automatically replicates objects from one S3 bucket to another. The replication can occur within the same AWS region (SRR) or across different regions (CRR). This ensures redundancy, availability, and compliance with organizational policies.&lt;/p&gt;
&lt;p&gt;AWS S3 Replication requires enabling Versioning on both the source and destination buckets. Once set up, it replicates newly uploaded objects asynchronously while preserving metadata, object tags, and Access Control Lists (ACLs), depending on your configurations.&lt;/p&gt;
&lt;h2 id=&quot;-same-region-replication-srr-&quot;&gt;&lt;strong&gt; Same-Region Replication (SRR)&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id=&quot;-what-is-srr-&quot;&gt;✅ What is SRR?&lt;/h3&gt;
&lt;p&gt;SRR allows replication of S3 objects within the same AWS region. This is useful for organizations that need multiple copies of data in the same geography but within different buckets.&lt;/p&gt;
&lt;h3 id=&quot;use-cases&quot;&gt;Use Cases&lt;/h3&gt;
&lt;p&gt;Compliance &amp;amp; Governance: Maintain multiple copies of data within a region to meet internal compliance regulations.&lt;/p&gt;
&lt;p&gt;Data Tiering &amp;amp; Organization: Separate different teams' data while keeping it in the same region for low latency.&lt;/p&gt;
&lt;p&gt;Backup &amp;amp; Disaster Recovery: Keep replicated copies in a different bucket to recover from accidental deletions or corruption.&lt;/p&gt;
&lt;p&gt;Logging &amp;amp; Analytics: Send logs to a centralized bucket for analysis without moving them across regions.&lt;/p&gt;
&lt;h3 id=&quot;-benefits&quot;&gt;⚡ Benefits&lt;/h3&gt;
&lt;p&gt;Low Latency: Since data is within the same region, replication is faster.&lt;/p&gt;
&lt;p&gt;Cost-Effective: No inter-region data transfer costs.&lt;/p&gt;
&lt;p&gt;Better Access Control: Different teams can access separate replicated buckets without modifying the source bucket’s policies.&lt;/p&gt;
&lt;h3 id=&quot;-considerations&quot;&gt;⚠️ Considerations&lt;/h3&gt;
&lt;p&gt;SRR does not protect against regional failures; for true disaster recovery, CRR is needed.&lt;/p&gt;
&lt;p&gt;There might be some added storage costs depending on data volume.&lt;/p&gt;
&lt;h2 id=&quot;-cross-region-replication-crr-&quot;&gt;&lt;strong&gt; Cross-Region Replication (CRR)&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id=&quot;-what-is-crr-&quot;&gt;✅ What is CRR?&lt;/h3&gt;
&lt;p&gt;CRR replicates objects across different AWS regions, providing geographical redundancy and enhanced disaster recovery.&lt;/p&gt;
&lt;h3 id=&quot;use-cases-1&quot;&gt;Use Cases&lt;/h3&gt;
&lt;p&gt;Disaster Recovery: Store copies of critical data in another region to protect against region-wide outages.&lt;/p&gt;
&lt;p&gt;Compliance &amp;amp; Regulations: Meet legal requirements for keeping data in multiple geographic locations.&lt;/p&gt;
&lt;p&gt;Global Content Distribution: Improve performance by serving data closer to end users in different regions.&lt;/p&gt;
&lt;p&gt;High Availability &amp;amp; Fault Tolerance: Maintain business continuity in case of failures in one region.&lt;/p&gt;
&lt;h3 id=&quot;-benefits-1&quot;&gt;⚡ Benefits&lt;/h3&gt;
&lt;p&gt;Resilient Disaster Recovery: Data is available in another region in case of primary region failure.&lt;/p&gt;
&lt;p&gt;Reduced Latency for Global Users: Store data closer to international customers to reduce access time.&lt;/p&gt;
&lt;p&gt;Improved Business Continuity: Ensures uninterrupted service availability.&lt;/p&gt;
&lt;h3 id=&quot;-considerations-1&quot;&gt;⚠️ Considerations&lt;/h3&gt;
&lt;p&gt;Higher Costs: Inter-region data transfer fees apply.&lt;/p&gt;
&lt;p&gt;Increased Latency: Replication takes longer due to network distance.&lt;/p&gt;
&lt;p&gt;Regulatory Compliance: Ensure cross-border data transfers align with local regulations.&lt;/p&gt;
&lt;h2 id=&quot;-setting-up-s3-replication-srr-amp-crr-&quot;&gt;️ Setting Up S3 Replication (SRR &amp;amp; CRR)&lt;/h2&gt;
&lt;p&gt;To enable replication:&lt;/p&gt;
&lt;p&gt;Enable versioning for both source and destination buckets.&lt;/p&gt;
&lt;p&gt;Set up IAM permissions for replication.&lt;/p&gt;
&lt;p&gt;Create a Replication Rule specifying:&lt;/p&gt;
&lt;p&gt;Source and destination buckets&lt;/p&gt;
&lt;p&gt;Filter conditions (prefix, object tags, etc.)&lt;/p&gt;
&lt;p&gt;Replication options (SRR or CRR)&lt;/p&gt;
&lt;p&gt;Enable S3 Replication Time Control (RTC) for predictable replication times (optional).&lt;/p&gt;
&lt;p&gt;Monitor replication progress using AWS CloudWatch and S3 Replication Metrics.&lt;/p&gt;
&lt;h2 id=&quot;srr-vs-crr-which-one-should-you-choose-&quot;&gt;SRR vs. CRR: Which One Should You Choose?&lt;/h2&gt;
&lt;p&gt;FeatureSame-Region Replication (SRR)Cross-Region Replication (CRR)&lt;br&gt;
Data Transfer CostLower (No inter-region cost)Higher (Inter-region transfer fees)&lt;br&gt;
LatencyLow (Faster replication)Higher (Depends on region distance)&lt;br&gt;
Disaster RecoveryNo protection from regional failuresHigh protection from region-wide failures&lt;br&gt;
ComplianceUseful for internal regulationsRequired for international regulations&lt;br&gt;
Global AccessLimited to one regionEnables worldwide data access&lt;/p&gt;
&lt;h2 id=&quot;best-practices-for-s3-replication&quot;&gt;Best Practices for S3 Replication&lt;/h2&gt;
&lt;p&gt;✅ Use Lifecycle Policies: Delete older versions or move replicated data to S3 Glacier for cost efficiency. &lt;br&gt;
✅ Monitor with CloudWatch: Keep track of replication success rates and failures. &lt;br&gt;
✅ Use Replication Time Control (RTC): Ensure predictable replication times. &lt;br&gt;
✅ Encrypt Replicated Data: Enable AWS KMS encryption to secure sensitive information. &lt;br&gt;
✅ Optimize Costs: Use Intelligent-Tiering for replicated objects to reduce expenses.&lt;/p&gt;
&lt;h2 id=&quot;conclusion-3&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Both Same-Region Replication (SRR) and Cross-Region Replication (CRR) are powerful features in AWS S3 that serve different business needs. SRR is ideal for compliance, backups, and data segregation, while CRR is essential for disaster recovery and global distribution. Choosing the right replication strategy depends on your use case, performance requirements, and budget.&lt;/p&gt;
&lt;p&gt;By implementing these best practices, you can ensure optimal data availability, security, and resilience in your AWS cloud environment.&lt;/p&gt;
&lt;p&gt; Ready to enhance your AWS storage strategy? Implement S3 replication today!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/1033/mastering-amazon-s3-replication-same-region-vs-cross-region-replication</guid>
<pubDate>Sun, 02 Feb 2025 09:34:30 +0000</pubDate>
</item>
<item>
<title>Choosing the Right Solana Wallet: Phantom vs. Solflare vs. Sollet</title>
<link>https://coderlegion.com/874/choosing-the-right-solana-wallet-phantom-vs-solflare-vs-sollet</link>
<description>&lt;h2 id=&quot;solana-wallets-choosing-the-best-one-for-developers&quot;&gt;Solana Wallets: Choosing the Best One for Developers&lt;/h2&gt;
&lt;p&gt;As Solana continues to gain traction for its high-speed, low-cost transactions, the importance of a reliable wallet for developers cannot be overstated. Whether you’re building decentralized applications (dApps), managing SPL tokens, or exploring blockchain interoperability, choosing the right wallet impacts both your development workflow and the user experience. In this article, we’ll compare &lt;strong&gt;Phantom&lt;/strong&gt;, &lt;strong&gt;Solflare&lt;/strong&gt;, and &lt;strong&gt;Sollet&lt;/strong&gt; wallets to help developers select the best one for their use cases.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;key-considerations-for-developers&quot;&gt;Key Considerations for Developers&lt;/h3&gt;
&lt;p&gt;Before diving into the comparison, here are some criteria developers should prioritize when choosing a Solana wallet:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ease of Integration&lt;/strong&gt;: Availability of SDKs, APIs, and Web3.js support.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer-Friendliness&lt;/strong&gt;: Debugging tools and testnet support.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: Robust security measures like seed phrase backup and private key encryption.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multichain Support&lt;/strong&gt;: Support for SPL tokens, NFTs, and other blockchain assets.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User Interface (UI)&lt;/strong&gt;: Intuitive UI for testing dApp functionalities.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-phantom-wallet&quot;&gt;1. Phantom Wallet&lt;/h3&gt;
&lt;h4 id=&quot;overview&quot;&gt;Overview&lt;/h4&gt;
&lt;p&gt;Phantom is the most popular Solana wallet, offering a sleek UI and seamless integration for dApps. It is widely regarded as the &quot;MetaMask of Solana.&quot;&lt;/p&gt;
&lt;h4 id=&quot;pros&quot;&gt;Pros&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Developer Tools&lt;/strong&gt;: Phantom supports easy integration with dApps via Solana Web3.js, making it ideal for developers building decentralized applications.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testnet Support&lt;/strong&gt;: Switch between mainnet, testnet, and devnet with ease, allowing developers to test features without using real funds.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NFT and SPL Token Support&lt;/strong&gt;: Displays NFTs and SPL tokens natively, simplifying development for NFT or token-centric applications.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User-Friendly Interface&lt;/strong&gt;: Intuitive design, making it easier to onboard users during testing phases.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cons&quot;&gt;Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Browser-Only&lt;/strong&gt;: Phantom is available as a browser extension, limiting its usage in mobile or hardware wallet setups.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited Advanced Features&lt;/strong&gt;: While great for general development, Phantom lacks some advanced debugging tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;ideal-use-case&quot;&gt;Ideal Use Case&lt;/h4&gt;
&lt;p&gt;For developers building user-facing dApps, Phantom offers unparalleled ease of use and seamless integration.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-solflare-wallet&quot;&gt;2. Solflare Wallet&lt;/h3&gt;
&lt;h4 id=&quot;overview-1&quot;&gt;Overview&lt;/h4&gt;
&lt;p&gt;Solflare is a versatile wallet designed for both casual users and developers. It supports browser, mobile, and hardware wallets, making it a more flexible choice.&lt;/p&gt;
&lt;h4 id=&quot;pros-1&quot;&gt;Pros&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multiplatform Support&lt;/strong&gt;: Available as a browser extension, mobile app, and desktop app, providing developers with multiple testing environments.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardware Wallet Integration&lt;/strong&gt;: Native support for Ledger devices, offering enhanced security for development purposes.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Comprehensive Token Support&lt;/strong&gt;: Excellent for managing SPL tokens, staking SOL, and interacting with DeFi protocols.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Advanced Features&lt;/strong&gt;: Offers in-app staking and advanced transaction visualizations, which can be useful for debugging.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cons-1&quot;&gt;Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Steeper Learning Curve&lt;/strong&gt;: The feature-rich interface can feel overwhelming for new developers.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed&lt;/strong&gt;: Slightly slower UI compared to Phantom.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;ideal-use-case-1&quot;&gt;Ideal Use Case&lt;/h4&gt;
&lt;p&gt;Solflare is perfect for developers requiring cross-platform testing or those who value hardware wallet integration for security.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-sollet-wallet&quot;&gt;3. Sollet Wallet&lt;/h3&gt;
&lt;h4 id=&quot;overview-2&quot;&gt;Overview&lt;/h4&gt;
&lt;p&gt;Sollet is an open-source wallet primarily targeted at developers. While less user-friendly, it offers unmatched transparency and customization options.&lt;/p&gt;
&lt;h4 id=&quot;pros-2&quot;&gt;Pros&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Open Source&lt;/strong&gt;: Ideal for developers who want to understand and customize wallet behavior.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Advanced Features&lt;/strong&gt;: Provides raw transaction data and manual signing options, making it suitable for low-level debugging.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom RPC Endpoints&lt;/strong&gt;: Easily configure RPC nodes, which is crucial for developers testing network-specific features.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cons-2&quot;&gt;Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Outdated UI&lt;/strong&gt;: The user interface feels clunky and dated compared to Phantom or Solflare.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Steep Learning Curve&lt;/strong&gt;: Beginners may find it challenging to navigate or understand the wallet’s features.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited Ecosystem Support&lt;/strong&gt;: Lacks native support for NFTs and modern dApp integrations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;ideal-use-case-2&quot;&gt;Ideal Use Case&lt;/h4&gt;
&lt;p&gt;Sollet is best for experienced developers who need granular control over transactions and want to work directly with Solana’s technical features.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;feature-comparison-table&quot;&gt;Feature Comparison Table&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature               &lt;/th&gt;&lt;th&gt;Phantom             &lt;/th&gt;&lt;th&gt;Solflare            &lt;/th&gt;&lt;th&gt;Sollet&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Ease of Integration   &lt;/td&gt;&lt;td&gt;Excellent              &lt;/td&gt;&lt;td&gt;Good                    &lt;/td&gt;&lt;td&gt;Moderate&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Testnet Support       &lt;/td&gt;&lt;td&gt;Yes                    &lt;/td&gt;&lt;td&gt;Yes                     &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;NFT Display           &lt;/td&gt;&lt;td&gt;Yes                    &lt;/td&gt;&lt;td&gt;Yes                     &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Hardware Wallets      &lt;/td&gt;&lt;td&gt;Limited                &lt;/td&gt;&lt;td&gt;Full                    &lt;/td&gt;&lt;td&gt;Full&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Custom RPC Endpoints  &lt;/td&gt;&lt;td&gt;Limited                &lt;/td&gt;&lt;td&gt;Yes                     &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Open Source           &lt;/td&gt;&lt;td&gt;No                     &lt;/td&gt;&lt;td&gt;No                      &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Mobile Support        &lt;/td&gt;&lt;td&gt;No                     &lt;/td&gt;&lt;td&gt;Yes                     &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;examples-of-dapps-where-wallets-excel&quot;&gt;Examples of dApps Where Wallets Excel&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phantom&lt;/strong&gt;: Seamless integration with dApps like &lt;strong&gt;Magic Eden&lt;/strong&gt; (NFT marketplace) and &lt;strong&gt;Orca&lt;/strong&gt; (DeFi exchange).  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Solflare&lt;/strong&gt;: Ideal for staking SOL on platforms like &lt;strong&gt;Marinade Finance&lt;/strong&gt; or interacting with DeFi apps like &lt;strong&gt;Saber&lt;/strong&gt;.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sollet&lt;/strong&gt;: Preferred for custom integrations with raw transactions in developer-heavy projects like &lt;strong&gt;Solana Program Library (SPL)&lt;/strong&gt; examples.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;beginner-friendly-resources-for-sollet&quot;&gt;Beginner-Friendly Resources for Sollet&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://sollet.io&quot;&gt;Sollet Documentation&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.solana.com/developing&quot;&gt;Solana Development Guide&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/results?search_query=sollet+wallet+guide&quot;&gt;Community Tutorials on YouTube&lt;/a&gt;  &lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;conclusion-4&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Each wallet serves a unique purpose in the Solana ecosystem, and the best choice depends on your specific development needs. For most dApp developers, &lt;strong&gt;Phantom&lt;/strong&gt; is the go-to choice for its simplicity and integration capabilities. However, for more advanced use cases or multi-platform testing, &lt;strong&gt;Solflare&lt;/strong&gt; and &lt;strong&gt;Sollet&lt;/strong&gt; provide features that can significantly enhance your workflow.&lt;/p&gt;
&lt;p&gt;By understanding the strengths and limitations of each wallet, you can choose the one that best aligns with your development goals. Let your choice of wallet be a tool that amplifies your innovation on Solana!&lt;/p&gt;
</description>
<category>Python</category>
<guid isPermaLink="true">https://coderlegion.com/874/choosing-the-right-solana-wallet-phantom-vs-solflare-vs-sollet</guid>
<pubDate>Mon, 20 Jan 2025 20:15:34 +0000</pubDate>
</item>
<item>
<title>Right-Sizing Microservices: Harnessing Integrators and Disintegrators for Striking the Perfect Balance</title>
<link>https://coderlegion.com/862/sizing-microservices-harnessing-integrators-disintegrators-striking-perfect-balance</link>
<description>&lt;h2 id=&quot;introduction-2&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Microservices have become the go-to approach for building modern software systems thanks to their promise of scalability, flexibility, and faster development. But behind the hype lies a critical truth: microservices can create more problems than they solve if not planned and sized correctly. Poorly designed microservices can lead to confusion, unnecessary complexity, and extra overhead, making your system harder to manage over time. The result? Long-term issues like skyrocketing costs, maintenance headaches, and a system that feels more like a tangled web than a streamlined solution.&lt;/p&gt;
&lt;p&gt;In this blog, we’ll dive into the concept of right-sizing microservices and explore how integrators and disintegrators can help you design an architecture that is scalable, efficient, and sustainable. Whether you’re just starting with microservices or looking to improve an existing setup, this article will give you the insights you need to avoid common pitfalls and get it right from the start. Let’s build smarter, not harder!&lt;/p&gt;
&lt;h2 id=&quot;importance-of-correct-sizing&quot;&gt;Importance of Correct Sizing&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=439508702524343876&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Getting the size of your microservices right is essential because both extremes — too large or too small — can lead to significant problems. When microservices are too large, they resemble monoliths, undermining the benefits of adopting a microservices architecture. Large services are harder to scale independently, making them less efficient in handling varying workloads. They also create deployment bottlenecks, as even small changes might require redeploying the entire service. This can lead to slower development cycles, increased downtime, and the loss of flexibility that microservices are supposed to provide.&lt;/p&gt;
&lt;p&gt;On the other hand, too small microservices can result in an overly fragmented and complex system. This often increases communication overhead between services, leading to higher latency, potential performance bottlenecks, and difficulties in management. Such fragmentation can also force teams to deal with complex distributed scenarios, such as managing distributed transactions across multiple services. These scenarios add significant development and operational overhead, as ensuring consistency and reliability becomes a non-trivial challenge. Striking the right balance is critical to avoid these pitfalls and ensure a system that is not only scalable but also maintainable and efficient.&lt;/p&gt;
&lt;h2 id=&quot;service-disintegrators&quot;&gt;Service Disintegrators&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=10148868327765658676&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Breaking down a service into smaller pieces is a critical decision in microservices architecture, and it’s not one to take lightly. Service disintegration should be guided by clear principles and justified by the specific needs of your system.&lt;/p&gt;
&lt;p&gt;In this section, we’ll explore six key drivers that help determine when and why you should split a service. These drivers provide a structured framework to evaluate the need for disintegration and ensure it aligns with your architecture’s scalability, maintainability, and performance goals. By understanding these factors, you can make informed decisions that balance flexibility and simplicity, avoiding the pitfalls of over-fragmentation.&lt;/p&gt;
&lt;h3 id=&quot;service-scope-and-function&quot;&gt;Service Scope and Function&lt;/h3&gt;
&lt;p&gt;One of the most common reasons to break a service into smaller parts is its scope and functionality. A key aspect of this is evaluating the service’s cohesion — how well its responsibilities are related to each other. A highly cohesive service focuses on a single, well-defined purpose, making it easier to maintain and understand. On the other hand, low cohesion indicates that a service is handling unrelated tasks, which can lead to confusion and make updates or troubleshooting more difficult. Size also plays a crucial role in deciding whether a service should be split. If a service grows too large, it becomes harder to test, deploy, and scale effectively.&lt;/p&gt;
&lt;p&gt;For example, consider a “User Management” service that handles user registration, authentication, profile updates, and permissions. These tasks are all related but serve distinct purposes. By analyzing cohesion, we can see that authentication (focused solely on verifying user identity) is separate in function from profile updates (which involve personal user details) or permissions (related to access controls). Splitting authentication into its own service ensures high cohesion and keeps the responsibilities clear.&lt;/p&gt;
&lt;p&gt;Correctly sizing services in this way also helps manage their size. The “Authentication” service, for instance, can now be scaled independently to handle login spikes without affecting profile or permissions functionality. This approach ensures that services are focused, manageable, and aligned with the principles of a scalable microservices architecture.&lt;/p&gt;
&lt;h3 id=&quot;code-volatility&quot;&gt;Code Volatility&lt;/h3&gt;
&lt;p&gt;Another key factor in determining whether to break a service into smaller parts is code volatility — the rate at which the source code changes. Services or components with high volatility often need frequent updates to add features, fix bugs, or respond to business needs. Keeping these components as independent services can reduce risks and improve maintainability.&lt;/p&gt;
&lt;p&gt;For instance, consider a “Reporting” service that generates standard reports but also includes a component for custom report generation. If the custom reporting logic changes frequently to meet new client demands, it would make sense to extract this into a separate service. This way, the volatile component can be updated and deployed independently without affecting the stable parts of the system.&lt;/p&gt;
&lt;p&gt;The benefits of such separation are significant. Independently deployable services mean faster updates without impacting other functionalities. Testing efforts are more focused, as the scope is limited to the specific service. Moreover, the blast radius of a failure is reduced, ensuring that issues in one service (e.g., custom reports) do not disrupt the overall system. Managing code volatility through independent services leads to a more resilient and flexible architecture.&lt;/p&gt;
&lt;h3 id=&quot;scalability-and-throughput&quot;&gt;Scalability and Throughput&lt;/h3&gt;
&lt;p&gt;When a service experiences extreme variations in scalability requirements, separating its components can significantly improve efficiency. Different parts of a service may have varying demands on resources, and splitting them allows you to scale each component independently based on its specific needs.&lt;/p&gt;
&lt;p&gt;For example, consider a “Notification” service that sends both SMS and Email notifications. Let’s assume that SMS notifications typically generate much higher traffic than emails, especially during peak usage periods, such as promotional campaigns or transactional alerts. By separating SMS and email functionalities into distinct services, you can scale the SMS service to handle the high traffic while keeping the email service infrastructure minimal and cost-effective.&lt;/p&gt;
&lt;p&gt;This approach offers several advantages. Only the components with higher demand — like SMS — are scaled, reducing unnecessary infrastructure provisioning for the less-demanding email service. This targeted scaling not only ensures optimal performance but also minimizes costs, making your architecture more efficient. By managing scalability and throughput through service separation, you can maintain a responsive, reliable system without overburdening your infrastructure.&lt;/p&gt;
&lt;h3 id=&quot;fault-tolerance&quot;&gt;Fault Tolerance&lt;/h3&gt;
&lt;p&gt;Fault tolerance is a critical factor in ensuring the reliability of your system. It refers to the ability of an application or specific functionality to continue operating even when a fatal crash occurs. Separating services based on their fault tolerance requirements helps isolate failures, preventing them from cascading across the entire system.&lt;/p&gt;
&lt;p&gt;For example, consider a “Food Delivery” platform that has a service for placing orders and another for real-time delivery tracking. If these are tightly coupled and the delivery tracking component fails (e.g., due to a third-party API outage or high traffic), it could potentially bring down the order placement functionality as well. By separating these into independent services, users can still place orders even if the delivery tracking service experiences issues.&lt;/p&gt;
&lt;p&gt;This separation ensures that critical operations, like order placement, continue functioning despite failures in non-critical components. Isolating such failures helps maintain a partially operational system, reduces downtime for key features, and improves user experience by minimizing the impact of outages.&lt;/p&gt;
&lt;h3 id=&quot;security&quot;&gt;Security&lt;/h3&gt;
&lt;p&gt;Security is a crucial consideration when designing microservices. Components with higher security requirements can be separated into independent services to implement specialized security measures without impacting other parts of the system.&lt;/p&gt;
&lt;p&gt;For instance, in an “E-Commerce” platform, payment processing demands stricter security protocols compared to general catalog browsing. By separating the payment service, you can deploy it independently with advanced security features such as encryption, tokenization, and stricter access controls. Meanwhile, the catalog browsing service can operate with standard security measures, optimizing performance while maintaining safety.&lt;/p&gt;
&lt;p&gt;This separation ensures that sensitive operations are protected with the highest level of security without overburdening less critical components. It also simplifies compliance with regulatory standards like PCI DSS for payments, making the overall system more secure and manageable. By isolating high-security components, you can better safeguard user data and reduce potential vulnerabilities in your architecture.&lt;/p&gt;
&lt;h3 id=&quot;extensibility&quot;&gt;Extensibility&lt;/h3&gt;
&lt;p&gt;Extensibility refers to the ability to add new functionality to a service as its context or business needs evolve. A well-structured microservice allows you to introduce new features or update existing ones without overhauling the entire system or causing disruptions.&lt;/p&gt;
&lt;p&gt;For example, consider a “Payment” service that handles various payment methods such as credit cards, PayPal, and bank transfers. As the business grows, you might need to introduce additional payment options like cryptocurrency, mobile wallets, or buy-now-pay-later services. Instead of reworking the entire payment service, you can extend its functionality by creating separate components for each payment method. This allows you to add new payment options without impacting existing ones, keeping the service flexible and scalable.&lt;/p&gt;
&lt;p&gt;By structuring the service this way, you avoid turning it into a large, monolithic component. New payment methods can be added with minimal disruption and without requiring frequent redeployments. Extensibility ensures that the system can grow as business needs change, keeping your architecture adaptable and efficient.&lt;/p&gt;
&lt;h2 id=&quot;service-integrators&quot;&gt;Service Integrators&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9632712910093163121&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;While breaking services apart into smaller, more manageable pieces is often beneficial, there are cases where it makes more sense to integrate services rather than separate them. In this section, we will provide guidance and justification for when to keep services together, or when it might be better not to break a service apart in the first place. Sometimes, splitting a service too early can introduce unnecessary complexity or overhead, making it harder to scale, maintain, or even evolve over time.&lt;/p&gt;
&lt;p&gt;We’ll explore four key aspects that help determine when service integration is the best approach, including factors such as operational complexity, communication overhead, data consistency, and future scalability.&lt;/p&gt;
&lt;h3 id=&quot;database-transactions&quot;&gt;Database Transactions&lt;/h3&gt;
&lt;p&gt;Database transactions are crucial for maintaining data integrity when multiple operations need to succeed or fail as a single unit of work. Keeping related functionalities within the same service can simplify this process, avoiding the complexity and challenges of distributed transactions when multiple services are involved.&lt;/p&gt;
&lt;p&gt;For instance, consider a “User Registration” service that handles creating user accounts and assigning default permissions. If these functionalities are split into separate services — one for account creation and another for permission management — ensuring that permissions are assigned only when the account is successfully created would require a distributed transaction. Distributed transactions are notoriously complex to implement, requiring advanced coordination mechanisms and increasing the risk of failure.&lt;/p&gt;
&lt;p&gt;By combining these functionalities into a single service, you can manage all database operations within a single transaction. This ensures that the user account and associated permissions are either both successfully created or rolled back together, maintaining data integrity and simplifying the overall system.&lt;/p&gt;
&lt;h3 id=&quot;workflow-and-choreography&quot;&gt;Workflow and Choreography&lt;/h3&gt;
&lt;p&gt;In microservices architectures, workflows often involve multiple services working together through interservice communication. While this enables flexibility and modularity, excessive communication can lead to performance bottlenecks, increased latency, and reduced fault tolerance.&lt;/p&gt;
&lt;p&gt;For instance, in a “Food Delivery” application, the order service might need to communicate with the payment service, the restaurant service, and the delivery assignment service. If these services are too decoupled, placing an order might require several network calls, increasing the chances of delays or failures. For example, if the delivery assignment service experiences an outage, it could block the entire order workflow, preventing users from completing their transactions.&lt;/p&gt;
&lt;p&gt;Consolidating services that are tightly coupled in their workflow — such as the order and delivery assignment services — can simplify communication, reduce latency, and improve fault tolerance. This approach ensures that the system remains responsive and reliable even under high workloads or partial failures.&lt;/p&gt;
&lt;h3 id=&quot;shared-code&quot;&gt;Shared Code&lt;/h3&gt;
&lt;p&gt;Shared code in microservices, typically managed through shared libraries, is often seen as a way to streamline development and maintain consistency. However, it can introduce challenges when it comes to service sizing and independence. When a shared library is updated, all dependent services need to align with the changes, which can disrupt their deployment cycles and create dependencies that counteract the autonomy of microservices.&lt;/p&gt;
&lt;p&gt;For example, imagine a “Customer Management” system where multiple services — such as a profile service, loyalty program service, and notification service — use a shared library for customer validation logic. If the library is updated to include new validation rules, all these services must integrate the update. If one service lags in updating, it can lead to inconsistencies in how customer data is validated across the system.&lt;/p&gt;
&lt;p&gt;When designing services, it’s important to evaluate whether shared code aligns with the desired level of autonomy. For highly independent services with distinct lifecycles, duplicating certain logic or adopting service-specific libraries might be preferable to avoid unnecessary coupling and to maintain flexibility in deployment and scaling.&lt;/p&gt;
&lt;h3 id=&quot;data-relationships&quot;&gt;Data Relationships&lt;/h3&gt;
&lt;p&gt;In a microservices architecture, the practice of having each service manage its own database ensures independence but complicates data relationships. When related data is distributed across services, maintaining relationships can become challenging, leading to potential data integrity issues and increased interservice communication.&lt;/p&gt;
&lt;p&gt;For example, consider a “Ride-Sharing” platform. Suppose there is a driver service managing driver profiles and a ride service managing trip data. If these services are too granular, storing driver details in one database and trip data in another, operations like generating a driver’s earnings report or assigning rides based on availability require constant communication between the two services. This adds unnecessary complexity and can lead to delays, especially under high traffic conditions.&lt;/p&gt;
&lt;p&gt;In this scenario, consolidating the driver and ride data into a single service with a shared database might simplify the architecture. This approach would ensure tighter data consistency, reduce the need for interservice communication, and improve the overall system’s responsiveness, highlighting the importance of appropriately sizing services based on data relationships.&lt;/p&gt;
&lt;h2 id=&quot;finding-the-right-balance&quot;&gt;Finding the Right Balance&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=5033029432454027528&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Determining the optimal size for a service is a balancing act that requires analyzing trade-offs between breaking services apart and consolidating them. While smaller services offer benefits like scalability and independence, they can introduce complexity through distributed communication and management. Conversely, larger services simplify workflows and data relationships but may sacrifice flexibility and autonomy.&lt;/p&gt;
&lt;p&gt;To find the right balance, it’s crucial to collaborate closely with business stakeholders. Understanding business priorities, such as the need for agility, fault tolerance, or cost optimization, helps inform decisions on service sizing. The goal is to align technical architecture with business objectives while ensuring the system remains scalable, reliable, and manageable over the long term.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-5&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;In conclusion, the journey to right-sizing microservices is a nuanced process that involves carefully balancing disintegration and integration factors. While disintegrating services can provide advantages such as flexibility, scalability, and fault tolerance, it also introduces challenges like increased communication complexity and potential data integrity issues. On the other hand, integrating services offers simplicity but can reduce the system’s agility and create dependencies that hinder scaling.&lt;/p&gt;
&lt;p&gt;Analyzing the trade-offs between disintegration drivers and integration drivers is the secret to getting service granularity right. By collaborating closely with business stakeholders, understanding their priorities, and evaluating the specific needs of each service, you can strike the right balance that ensures a scalable, maintainable, and cost-effective architecture. With the right approach, microservices can provide the benefits they promise without the complexities that come from poor sizing decisions.&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/862/sizing-microservices-harnessing-integrators-disintegrators-striking-perfect-balance</guid>
<pubDate>Sun, 19 Jan 2025 03:36:56 +0000</pubDate>
</item>
</channel>
</rss>