Stories · Season 3 — Magpie · Episode 02
The Five Things That Should Happen When Money Moves
In which a deposit needs to do five things, the deposit code does all five, and adding a sixth means touching it again.

The story
When money landed in a Magpie account, a few things were supposed to happen. Send
a push notification. Update the running ledger. Run a fraud check. Award
round-up points. Email a receipt if the customer opted in. Simple enough — so the
DepositFunds handler did all five, one after another, right there in the same
method.
Then it grew. Marketing wanted a “first deposit” celebration. Compliance wanted large deposits flagged. The data team wanted an analytics event. Each new requirement meant opening the deposit handler and adding another block, until the method that was supposed to be about depositing money was a 200-line to-do list of everything that should happen afterwards.
The day it broke was the day the email service had an outage. The receipt step threw, and because it was inline with everything else, it took the whole deposit down with it. Customers’ money didn’t land — not because the deposit failed, but because the receipt email failed. Lena in support spent the morning explaining to people that their money was fine, it just looked like it wasn’t.
“Why,” asked Ada, the founder, “did a broken email stop us from taking money?” Kemi didn’t have a good answer. The deposit knew far too much about everything that wasn’t depositing.
Why this is hard the traditional way
When a single action needs to cause many reactions, the path of least resistance is to do them all inline. It’s fine with two. It’s a liability with eight. The core operation becomes coupled to every downstream concern, so every new reaction is a change to critical money-moving code, and every downstream wobble (a slow email, a flaky fraud API) becomes a failure of the thing itself.
You also lose the ability to reason about it. “What happens when money is deposited?” should have a clear answer; instead it’s “read these 200 lines and hope you spot all the branches.” Reactions that have nothing to do with each other — email and analytics — are now entangled, and a bug in one can break the others.
How Relay changes the ending
In Relay, the aggregate raises a domain event — “this happened” — and
anything that needs to react subscribes independently. The deposit’s job is to
deposit and announce FundsDeposited. The notification, the ledger, the fraud
check, the points, the receipt each become their own small handler.
// The deposit does one thing and announces it.
account.Apply(new FundsDeposited(amount));
// Each reaction lives on its own, added without touching the deposit.
public class SendReceipt : IDomainEventHandler<FundsDeposited> { /* ... */ }
public class AwardRoundUpPoints : IDomainEventHandler<FundsDeposited> { /* ... */ }
public class FlagLargeDeposits : IDomainEventHandler<FundsDeposited> { /* ... */ }
Adding the sixth reaction is now a new file, not a risky edit to money-moving
code. Reactions are isolated, so a struggling email service is a failed receipt —
not a failed deposit. And “what happens when money is deposited?” has an honest
answer: list the handlers for FundsDeposited. The deposit got its focus back,
and the to-do list got out of its way.
What it costs you to ignore this
- Core logic becomes a junk drawer. The most important code accumulates every unrelated concern until nobody dares change it.
- One weak reaction sinks the whole action. Inline side effects mean a flaky dependency can fail the operation it was only supposed to follow.
- Every new requirement is a risky edit. Adding a reaction shouldn’t mean reopening the code that moves money.
- Behavior becomes unknowable. Tangled side effects hide what your system actually does when something important happens.