<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>Relay — articles &amp; stories</title><description>Tutorials and stories from Relay, the DDD / CQRS / event-sourcing framework for .NET.</description><link>https://relay.nuvoralabs.dev/</link><item><title>Article 010 — Authorization</title><link>https://relay.nuvoralabs.dev/articles/authorization/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/authorization/</guid><description>Authorization in Relay is declared on the message, not the endpoint.</description></item><item><title>Article 032 — Automated Blue/Green Projection Rebuilds</title><link>https://relay.nuvoralabs.dev/articles/automated-blue-green-rebuilds/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/automated-blue-green-rebuilds/</guid><description>Article 008 gave you the shadow (blue/green) rebuild: a projection implements IRebuildableProjection, IProjectionRebuildManager builds a fresh shadow read model from history while the live one keeps serving, and once the shadow has caught up an operator calls SwapAsync to atomically promote it.</description></item><item><title>Article 009 — Caching Queries</title><link>https://relay.nuvoralabs.dev/articles/caching-queries/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/caching-queries/</guid><description>A read model (article 007) already makes queries fast — a single indexed SELECT.</description></item><item><title>Article 006 — Concurrency &amp; Snapshots</title><link>https://relay.nuvoralabs.dev/articles/concurrency-and-snapshots/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/concurrency-and-snapshots/</guid><description>Article 005&apos;s event sourcing works beautifully for a fresh account with three events. Production breaks that idyll two ways.</description></item><item><title>Article 035 — Crypto-Shredding for the Right to Erasure</title><link>https://relay.nuvoralabs.dev/articles/crypto-shredding-right-to-erasure/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/crypto-shredding-right-to-erasure/</guid><description>An event-sourced system has a property its operators love and its compliance officers fear: the log is immutable and never deletes.</description></item><item><title>Article 023 — Distributed Coordination: Locks, Leader Election, Partitioning</title><link>https://relay.nuvoralabs.dev/articles/distributed-coordination/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/distributed-coordination/</guid><description>You scale a service by running more copies of it — but now those copies can step on each other: two instances run the same nightly job, or both try to advance the same projection, or process the same key out of order.</description></item><item><title>Article 003 — Domain Events</title><link>https://relay.nuvoralabs.dev/articles/domain-events/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/domain-events/</guid><description>In article 002 the Order aggregate raised domain events, but they just sat in a list — nothing reacted to them. This article closes that loop.</description></item><item><title>Article 004 — Errors &amp; HTTP</title><link>https://relay.nuvoralabs.dev/articles/errors-and-http/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/errors-and-http/</guid><description>A handler&apos;s job is to express what went wrong in business terms — &quot;this payment was not found,&quot; &quot;the amount exceeds the limit,&quot; &quot;the card was declined&quot; — not to know that those map to HTTP 404, 422, and 402.</description></item><item><title>Article 034 — Event Archiving &amp; Compaction</title><link>https://relay.nuvoralabs.dev/articles/event-archiving-and-compaction/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/event-archiving-and-compaction/</guid><description>An event store is append-only: events are never updated and never deleted, which is exactly what makes it a trustworthy source of truth.</description></item><item><title>Article 005 — Event Sourcing Basics</title><link>https://relay.nuvoralabs.dev/articles/event-sourcing-basics/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/event-sourcing-basics/</guid><description>Every sample so far stored state: the current product, the current order, the current balance.</description></item><item><title>Article 030 — Fenced Leadership &amp; Cluster Membership</title><link>https://relay.nuvoralabs.dev/articles/fenced-leadership-and-membership/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/fenced-leadership-and-membership/</guid><description>Article 023 elects a leader the simple way: a connection-bound ILeaderElector holds a PostgreSQL advisory lock for as long as its connection lives, and the database drops that lock when the process dies.</description></item><item><title>Article 002 — Your First Aggregate</title><link>https://relay.nuvoralabs.dev/articles/first-aggregate/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/first-aggregate/</guid><description>In article 001 the Product was a plain record and the &quot;store&quot; was a dictionary.</description></item><item><title>Article 001 — Getting Started: Commands, Queries, and the Mediator</title><link>https://relay.nuvoralabs.dev/articles/getting-started/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/getting-started/</guid><description>Relay is a framework for building .NET services around CQRS (Command Query Responsibility Segregation) and an in-process mediator.</description></item><item><title>Article 013 — The Inbox Pattern</title><link>https://relay.nuvoralabs.dev/articles/inbox/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/inbox/</guid><description>The outbox guarantees at-least-once delivery — which means the same message can arrive twice (a broker redelivery, an outbox re-publish after a crash).</description></item><item><title>Article 026 — Inline &amp; Multi-Stream Projections</title><link>https://relay.nuvoralabs.dev/articles/inline-and-multistream-projections/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/inline-and-multistream-projections/</guid><description>Article 007 built the standard read side: a projection runs in a background host, catches up from a checkpoint after each command commits, and is eventually consistent.</description></item><item><title>Article 031 — Intra-Projection Sharding: Parallel Lanes for One Hot Projection</title><link>https://relay.nuvoralabs.dev/articles/intra-projection-sharding/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/intra-projection-sharding/</guid><description>Article 008 makes a deliberate promise: a projection has exactly one active worker.</description></item><item><title>Article 012 — Messaging &amp; Transports</title><link>https://relay.nuvoralabs.dev/articles/messaging-and-transports/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/messaging-and-transports/</guid><description>The outbox stages events; a transport carries them.</description></item><item><title>Article 020 — Multi-Tenancy: Resolution, Enforcement, Isolation</title><link>https://relay.nuvoralabs.dev/articles/multi-tenancy/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/multi-tenancy/</guid><description>A multi-tenant service serves many customers (tenants) from shared infrastructure while guaranteeing that one tenant can never see another&apos;s data.</description></item><item><title>Article 021 — Observability: Telemetry, Metrics, Tracing, Health</title><link>https://relay.nuvoralabs.dev/articles/observability/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/observability/</guid><description>You cannot operate what you cannot see. Relay is instrumented with OpenTelemetry-native primitives out of the box — no wrappers, no proprietary format.</description></item><item><title>Article 037 — Operational Event Queries: A Forensic Scan over the Log</title><link>https://relay.nuvoralabs.dev/articles/operational-event-queries/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/operational-event-queries/</guid><description>The event store is an append-only log, and article 005 is emphatic about the rule that follows from that: never query the event store for reads.</description></item><item><title>Article 011 — The Outbox Pattern</title><link>https://relay.nuvoralabs.dev/articles/outbox/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/outbox/</guid><description>A command often needs to do two things: change its own state (save the order) and tell other services about it (publish OrderPlaced).</description></item><item><title>Article 033 — Persistent Named Subscriptions</title><link>https://relay.nuvoralabs.dev/articles/persistent-named-subscriptions/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/persistent-named-subscriptions/</guid><description>A projection (article 007) is a catch-up reader: it walks the global event log forward, updates a read model, and remembers how far it got so a restart resumes instead of reprocessing two years of history.</description></item><item><title>Article 028 — Pessimistic Per-Aggregate Write Lock</title><link>https://relay.nuvoralabs.dev/articles/pessimistic-aggregate-locking/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/pessimistic-aggregate-locking/</guid><description>Article 006 established Relay&apos;s default for concurrent writes to an event-sourced aggregate: optimistic concurrency.</description></item><item><title>Article 008 — Projection Operations</title><link>https://relay.nuvoralabs.dev/articles/projection-operations/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/projection-operations/</guid><description>Article 007&apos;s projection host works while every event is well-behaved. Production guarantees two things that break that assumption.</description></item><item><title>Article 007 — Projections</title><link>https://relay.nuvoralabs.dev/articles/projections/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/projections/</guid><description>Event sourcing stores what happened, which is perfect for the write side and useless for the read side.</description></item><item><title>Article 019 — Recurring Jobs</title><link>https://relay.nuvoralabs.dev/articles/recurring-jobs/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/recurring-jobs/</guid><description>Article 018 scheduled one-shot future work. This article covers the recurring case — &quot;every night at 2am,&quot; &quot;every Monday,&quot; &quot;every 15 minutes&quot; — and the job as the unit of recurring/background work.</description></item><item><title>Article 025 — Reference Architecture: A Production Service End-to-End</title><link>https://relay.nuvoralabs.dev/articles/reference-architecture/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/reference-architecture/</guid><description>The previous twenty-four articles each isolated one capability.</description></item><item><title>Article 014 — Reliable Messaging (End to End)</title><link>https://relay.nuvoralabs.dev/articles/reliable-messaging/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/reliable-messaging/</guid><description>The previous three articles built the pieces; this one assembles them into the complete, reliable, cross-service flow:</description></item><item><title>Article 022 — Resiliency: Retries, Circuit Breakers, Rate &amp; Concurrency Limits</title><link>https://relay.nuvoralabs.dev/articles/resiliency/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/resiliency/</guid><description>Distributed systems fail in partial, transient ways: a database blips, a downstream API times out, a queue backs up.</description></item><item><title>Article 029 — Rich Event Metadata</title><link>https://relay.nuvoralabs.dev/articles/rich-event-metadata/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/rich-event-metadata/</guid><description>Every event Relay appends already carries a little metadata: the CommandName that produced it and a Timestamp.</description></item><item><title>Article 017 — Routing Slips &amp; Compensation</title><link>https://relay.nuvoralabs.dev/articles/routing-slips/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/routing-slips/</guid><description>A distributed process books a flight, a hotel, and a car across three services. The flight and hotel succeed; the car fails.</description></item><item><title>Article 015 — Sagas</title><link>https://relay.nuvoralabs.dev/articles/sagas/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/sagas/</guid><description>A command changes one aggregate; reliable messaging lets services react to each other.</description></item><item><title>Article 018 — Scheduling</title><link>https://relay.nuvoralabs.dev/articles/scheduling/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/scheduling/</guid><description>Some work shouldn&apos;t happen now — it should happen later: send a reminder in 30 minutes, expire an unpaid order in an hour, retry a failed call after a backoff.</description></item><item><title>Article 024 — Schema Evolution: Upcasters, Stable Names, Migrations</title><link>https://relay.nuvoralabs.dev/articles/schema-evolution/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/schema-evolution/</guid><description>In an event-sourced system the events are the database, and unlike rows you can never UPDATE them — history is immutable.</description></item><item><title>Article 036 — Standalone Courier Activities</title><link>https://relay.nuvoralabs.dev/articles/standalone-courier-activities/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/standalone-courier-activities/</guid><description>017 showed compensation inside a saga: forward legs record undo-commands onto a durable itinerary, and a failure replays them in reverse as reliable, scheduled commands.</description></item><item><title>Article 016 — State-Machine Sagas</title><link>https://relay.nuvoralabs.dev/articles/state-machine-sagas/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/state-machine-sagas/</guid><description>Article 015 authored a saga imperatively — handler methods that mutate state and call RequestTimeout/Complete.</description></item><item><title>Article 027 — Time-Travel &amp; Live Aggregation: Reading the Past from the Stream</title><link>https://relay.nuvoralabs.dev/articles/time-travel-and-live-aggregation/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/articles/time-travel-and-live-aggregation/</guid><description>Event sourcing stores the sequence of events that produced an aggregate&apos;s state, and the write side rebuilds the current state by replaying that sequence (article 005).</description></item><item><title>Story 010 — How a Tiny Team Shipped Like a Big One</title><link>https://relay.nuvoralabs.dev/stories/how-a-tiny-team-shipped-like-a-big-one/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/how-a-tiny-team-shipped-like-a-big-one/</guid><description>The payoff: five people, big-system guarantees, no army required</description></item><item><title>Story 006 — The 2 A.M. Page Nobody Could Read</title><link>https://relay.nuvoralabs.dev/stories/the-2am-page-nobody-could-read/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-2am-page-nobody-could-read/</guid><description>Something&apos;s broken, the dashboard is green, and nobody can find the request</description></item><item><title>Story 023 — The 500 That Leaked the Stack Trace</title><link>https://relay.nuvoralabs.dev/stories/the-500-that-leaked-the-stack-trace/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-500-that-leaked-the-stack-trace/</guid><description>Inconsistent errors, leaked internals, and a partner who couldn&apos;t integrate</description></item><item><title>Story 021 — The Account That Went Negative</title><link>https://relay.nuvoralabs.dev/stories/the-account-that-went-negative/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-account-that-went-negative/</guid><description>The &quot;impossible&quot; balance, because the rules lived in the UI, not the model</description></item><item><title>Story 009 — The Auth Check Everyone Forgot</title><link>https://relay.nuvoralabs.dev/stories/the-auth-check-everyone-forgot/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-auth-check-everyone-forgot/</guid><description>An endpoint shipped wide open, and the code review didn&apos;t catch it</description></item><item><title>Story 005 — The Black Friday That Melted</title><link>https://relay.nuvoralabs.dev/stories/the-black-friday-that-melted/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-black-friday-that-melted/</guid><description>One slow dependency took down everything, then the retries finished the job</description></item><item><title>Story 002 — The Case of the Missing $4,000</title><link>https://relay.nuvoralabs.dev/stories/the-case-of-the-missing-4000/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-case-of-the-missing-4000/</guid><description>A balance is wrong and nobody can say why — there&apos;s no history</description></item><item><title>Story 026 — The Customer View That Was Always a Bit Wrong</title><link>https://relay.nuvoralabs.dev/stories/the-customer-view-that-was-always-a-bit-wrong/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-customer-view-that-was-always-a-bit-wrong/</guid><description>Stale-after-write reads and an incoherent multi-source dashboard</description></item><item><title>Story 018 — The Customer Who Asked to Be Forgotten</title><link>https://relay.nuvoralabs.dev/stories/the-customer-who-asked-to-be-forgotten/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-customer-who-asked-to-be-forgotten/</guid><description>An immutable log met the legal right to be erased</description></item><item><title>Story 004 — The Email That Sent Itself Five Times</title><link>https://relay.nuvoralabs.dev/stories/the-email-that-sent-itself-five-times/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-email-that-sent-itself-five-times/</guid><description>One event, delivered twice, charged the customer twice</description></item><item><title>Story 017 — The Field We Forgot to Add</title><link>https://relay.nuvoralabs.dev/stories/the-field-we-forgot-to-add/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-field-we-forgot-to-add/</guid><description>New code couldn&apos;t read three years of old, immutable events</description></item><item><title>Story 022 — The Five Things That Should Happen When Money Moves</title><link>https://relay.nuvoralabs.dev/stories/the-five-things-that-should-happen-when-money-moves/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-five-things-that-should-happen-when-money-moves/</guid><description>One action did five jobs inline; a broken email stopped a deposit</description></item><item><title>Story 028 — The Fraud Team Who Showed Up Late</title><link>https://relay.nuvoralabs.dev/stories/the-fraud-team-who-showed-up-late/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-fraud-team-who-showed-up-late/</guid><description>A new consumer needed all of history, then live — without losing its place</description></item><item><title>Story 029 — The Log That Ate the Storage Budget</title><link>https://relay.nuvoralabs.dev/stories/the-log-that-ate-the-storage-budget/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-log-that-ate-the-storage-budget/</guid><description>Keeping everything forever became the biggest line item</description></item><item><title>Story 014 — The Nightly Job That Quietly Stopped</title><link>https://relay.nuvoralabs.dev/stories/the-nightly-job-that-quietly-stopped/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-nightly-job-that-quietly-stopped/</guid><description>A cron job on one box died silently and lost two weeks of books</description></item><item><title>Story 024 — The Onboarding That Got Lost in If-Statements</title><link>https://relay.nuvoralabs.dev/stories/the-onboarding-that-got-lost-in-if-statements/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-onboarding-that-got-lost-in-if-statements/</guid><description>A workflow modeled as flag-soup, with impossible states in production</description></item><item><title>Story 008 — The Order That Got Stuck Forever</title><link>https://relay.nuvoralabs.dev/stories/the-order-that-got-stuck-forever/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-order-that-got-stuck-forever/</guid><description>A five-step process died on step three and left money in limbo</description></item><item><title>Story 019 — The Partner Who Spoke a Different Language</title><link>https://relay.nuvoralabs.dev/stories/the-partner-who-spoke-a-different-language/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-partner-who-spoke-a-different-language/</guid><description>Every new partner ran a different message broker</description></item><item><title>Story 003 — The Payment That Published But Never Happened</title><link>https://relay.nuvoralabs.dev/stories/the-payment-that-published-but-never-happened/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-payment-that-published-but-never-happened/</guid><description>The database saved it but the message never sent — or vice versa</description></item><item><title>Story 015 — The Payout That Ran Twice</title><link>https://relay.nuvoralabs.dev/stories/the-payout-that-ran-twice/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-payout-that-ran-twice/</guid><description>Redundancy without coordination paid every venue six times</description></item><item><title>Story 016 — The Read Model That Lied</title><link>https://relay.nuvoralabs.dev/stories/the-read-model-that-lied/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-read-model-that-lied/</guid><description>A fixed bug&apos;s wrong numbers were stuck in the dashboard forever</description></item><item><title>Story 013 — The Reminder That Arrived a Day Late</title><link>https://relay.nuvoralabs.dev/stories/the-reminder-that-arrived-a-day-late/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-reminder-that-arrived-a-day-late/</guid><description>A scan-and-send loop sent duplicates, gaps, and 3 a.m. emails</description></item><item><title>Story 001 — The Spreadsheet That Ran the Warehouse</title><link>https://relay.nuvoralabs.dev/stories/the-spreadsheet-that-ran-the-warehouse/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-spreadsheet-that-ran-the-warehouse/</guid><description>Every feature takes longer than the last; the code has turned to soup</description></item><item><title>Story 007 — The Tenant Who Saw Someone Else&apos;s Data</title><link>https://relay.nuvoralabs.dev/stories/the-tenant-who-saw-someone-elses-data/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-tenant-who-saw-someone-elses-data/</guid><description>One forgotten WHERE clause became a customer-facing breach</description></item><item><title>Story 012 — The Ticket Page That Hugged the Database to Death</title><link>https://relay.nuvoralabs.dev/stories/the-ticket-page-that-hugged-the-database-to-death/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-ticket-page-that-hugged-the-database-to-death/</guid><description>Going viral became an outage; lookers crowded out buyers</description></item><item><title>Story 027 — The Transaction Feed That Fell Behind</title><link>https://relay.nuvoralabs.dev/stories/the-transaction-feed-that-fell-behind/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-transaction-feed-that-fell-behind/</guid><description>One sequential projection couldn&apos;t keep up on payday</description></item><item><title>Story 025 — The Transfer That Needed Undoing</title><link>https://relay.nuvoralabs.dev/stories/the-transfer-that-needed-undoing/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-transfer-that-needed-undoing/</guid><description>A folder of one-off &quot;undo money&quot; scripts, none of them auditable</description></item><item><title>Story 011 — The Two People Who Bought the Last Seat</title><link>https://relay.nuvoralabs.dev/stories/the-two-people-who-bought-the-last-seat/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/the-two-people-who-bought-the-last-seat/</guid><description>Two buyers, one seat, a race condition in correct-looking code</description></item><item><title>Story 030 — What Did My Balance Say Last Tuesday?</title><link>https://relay.nuvoralabs.dev/stories/what-did-my-balance-say-last-tuesday/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/what-did-my-balance-say-last-tuesday/</guid><description>The payoff: customer, regulator, and lawyer all ask about the past — and get answers</description></item><item><title>Story 020 — Who Did This, and When?</title><link>https://relay.nuvoralabs.dev/stories/who-did-this-and-when/</link><guid isPermaLink="true">https://relay.nuvoralabs.dev/stories/who-did-this-and-when/</guid><description>The payoff: 5,000 mystery refunds, answered precisely from the log</description></item></channel></rss>