Greenbox delivers weekly produce boxes from local farms. With 2,500 subscribers and a team growing from five to twelve, the startup is expanding to Melbourne, and the codebase that was built fast for a small team is starting to crack under the weight.
Maya is chopping sweet potato when her phone rings. She’s at home in Fremantle, Saturday evening, Nadia’s Spotify playlist filling the kitchen. Nadia is making a dressing at the counter. The number on the screen is Dave Morrison’s.
Dave doesn’t call on weekends. He doesn’t call much at all, he’s a text message man, and even those are sparse. Three words. “Zucchini looks short.” Maya puts down the knife and answers.
“Maya. That Freshly mob rang me today.”
He says it the way he says everything: flat, unhurried, like he’s reporting rainfall. Maya leans against the counter. Nadia glances over.
“They’re offering guaranteed volume. A hundred crates a week. That’s more than you take from me in a month.”
Maya’s mouth goes dry. “Are you going to switch?”
A pause. Dave doesn’t rush pauses. “I didn’t say that. I said they called. Thought you should know.”
They talk for another two minutes. Dave mentions that Rachel got the same call. He says “goodnight, Maya” and hangs up.
Maya puts the phone face-down on the counter. Nadia has stopped whisking.
“What happened?”
“The thing I was afraid of.”
She tells Nadia about Freshly — the $12 million in funding, the guaranteed volume they’re dangling in front of the farms Greenbox depends on. A phone call to Dave is different from a competitor entering a market. That’s someone reaching for the thing she built.
The sweet potato burns slightly while they talk. They eat it anyway.
The 47-file PR
Greenbox has two thousand five hundred subscribers. The team is growing from five to twelve. They’re opening operations in Melbourne. And the codebase that Tom and Priya built for 200 subscribers is groaning under the weight.
Charlotte is now the team’s scaling coach. She’s spent fifteen years with subscription businesses and she’s seen what happens when a startup codebase meets rapid growth. Lee is still around for discovery foundations. But the problems now are different — not “we don’t understand the domain” but “the architecture can’t keep up.”
The pull request that said everything
Kai joins the team on a Monday. He’s twenty-eight, from Sydney, five years at a fintech company where he built payment systems handling half a billion dollars a year. Solid Go skills, comfortable with LLMs, and he carries the quiet confidence of someone who has never worked on a codebase he couldn’t master in a week.
With Kai joining, Tom realises the deploy script doesn’t scale — two developers deploying simultaneously caused a conflict last week. Tom sets up a basic CI/CD pipeline: tests run automatically, deploys go through a single pipeline instead of individual laptops. Priya’s GitHub Action from the BDD work evolves into a real pipeline.
Kai reads the codebase over two days. On Wednesday, he opens his LLMA neural network trained to predict the next token in a sequence, large enough that it generalises to tasks it wasn’t explicitly trained for. and prompts: “Add a gift subscription feature to this codebase. A customer should be able to buy a subscription as a gift for someone else.”
The LLM generates code. Kai reviews it, tweaks a few things, writes tests, and opens a pull request on Thursday afternoon.
The PR touches 47 files.
Across every part of the system. The subscription model, the payment processing, the delivery scheduling, the farm matching algorithm, the email templates, the customer portal. The gift subscription feature reaches into every corner because the codebase has no corners. It’s one big room.
One of the changes modifies the farm matching algorithm — it assumes supply is reliable enough to serve gift recipients on the same schedule as regular subscribers. Dave Morrison, whose zucchini yield over-promises by twenty percent every spring, would have something to say about that. But Dave isn’t in the code review.
Tom reviews the PR and his heart sinks. Not because the code is bad — some of the function signatures are cleaner than his own. But every change is tangled with everything else. Changing gift billing requires touching the same files as regular billing. The delivery changes affect all subscribers. The farm matching modifications could break Maya’s substitution logic.
“I can’t review this,” Tom tells Kai honestly. “Not because it’s wrong. Because I can’t tell what it’ll break.”
Charlotte pulls up the PR. “This is a symptom, not a bug.”
What Charlotte sees
She asks the team: “When you say ‘subscription,’ what do you mean?”
Tom: “The record that tracks what box someone gets and when they’re billed.”
Priya: “The relationship between a customer and their delivery schedule.”
Sam: “The thing a customer signs up for and can pause or cancel.”
Maya: “The commitment to receive a box every week.”
Four people. Four definitions. None wrong. All different.
“That’s your problem. Not four definitions — four definitions living in one codebase with no boundaries. When Kai asked the LLM to add gift subscriptions, the LLM did what the codebase told it to: spread the feature across everything, because everything is connected to everything.”
Bounded Contexts
Charlotte introduces Domain-Driven Design — specifically, Eric Evans’ concept of Bounded Contexts. Complex systems should be divided into distinct areas, each with its own clear language and boundaries.
“Subscription” in the billing context means “a recurring charge.” In the fulfilment context, it means “a delivery schedule.” In the customer context, it means “a thing I signed up for.” These aren’t contradictions. They’re different perspectives that belong in different parts of the code.
Charlotte pulls up the Event Storm photographs from months ago — Maya had them laminated, which Charlotte says is one of the smartest things she’s seen a founder do. “We’re going to run the next level up from what you did with Lee,” she says. “Lee got you a Process Level model — the flow, the events, the commands, the actors. Today we’re going to Event Storm an Architecture on top of it. Same wall, same sticky notes, but we’re looking for the code boundaries instead of the business logic.”
She copies the domain events onto the whiteboard and asks the team to help her find the boundaries.
“Look for three things,” she says. “First: where the language changes. When ‘subscription’ stops meaning the same thing to different people, that’s a boundary. Second: where the people change. The person who cares about supply matching is not the same person who cares about billing. Different stakeholders, different contexts. Third: where the rate of change differs. Billing changes when Stripe changes their API. Fulfilment changes when you add a city. If two areas change for different reasons, they probably belong in different contexts.”
The team clusters the events on the whiteboard. Tom moves “Payment Charged” next to “Invoice Generated”, they’re both about money. Priya groups “Farm Availability Submitted” with “Substitution Applied”, they’re both about what goes in the box. Sam pulls “Box Packed” and “Delivery Confirmed” together, those are her world.
Charlotte watches and asks questions. “Who cares when a payment fails?” Sam says billing. “Who cares when a box is packed?” Sam says logistics. “Who cares when a subscription is paused?” Everyone hesitates, it affects billing AND delivery. Charlotte marks it with a pink note: “Pause is a boundary event. It starts in one context and triggers work in others.”
After twenty minutes, four clusters have emerged. Not because Charlotte drew them, because the team found them by looking at who cares about what and when the language shifts:
- Customer Subscribed
- Subscription Paused
- Subscription Cancelled
- Gift Subscription Created
- Box Size Changed
- Payment Charged
- Payment Failed
- Invoice Generated
- Refund Issued
- Farm Availability Submitted
- Supply Matched to Demand
- Substitution Applied
- Shortfall Detected
- Box Packed
- Box Dispatched
- Delivery Confirmed
- Delivery Failed
Four bounded contexts. Each talks to the others through events and clearly defined interfaces. Inside each context, the code is self-contained. You can change billing without touching fulfilment.
The reprompt
Charlotte has Kai try again. This time with a bounded PromptThe input you hand to an LLM – system instructions, user message, examples, retrieved documents, tool descriptions, the lot. :
Add a gift subscription to the Subscription context. A gift subscription is created by a purchasing customer for a recipient. It has a status (pending, activated, active, paused, cancelled), a box size, a purchaser reference, and a recipient email. When created, publish a GiftSubscriptionCreated event. When activated, publish GiftSubscriptionActivated. The Subscription context does not handle billing, delivery, or supply matching.
The LLM generates code. The PR touches 8 files. The codebase is still one big room, nothing has been carved into packages yet, but the changes cluster: the subscription model, its status transitions, the gift fields, the two new events. Nothing reaches into billing, delivery, or farm matching.
Eight instead of forty-seven. Tom can review it in twenty minutes.
“The boundary didn’t just organise the code,” Charlotte says. “It organised the conversation with the LLM. We haven’t moved a single line yet, we just told it where the line is going to be.”
The Context Map
The bounded contexts communicate through events. Charlotte draws a Context Map showing what flows between them:
| From | To | Events |
|---|---|---|
| Subscription | Billing | SubscriptionCreated, SubscriptionPaused, SubscriptionCancelled |
| Subscription | Supply Matching | SubscriptionCreated, SubscriptionCancelled |
| Supply Matching | Fulfilment | BoxAllocated, SubstitutionApplied |
| Billing | Fulfilment | PaymentConfirmed |
This loose coupling is what the team is aiming for. Once the contexts are real in the code, Kai can build gift subscriptions while Priya works on Melbourne delivery zones, and their changes won’t collide.
Tom’s resistance
Tom pushes back. “This feels like Java-enterprise-architect nonsense. We’re a startup. We have twelve people, not twelve hundred.”
Charlotte doesn’t dismiss him. “You’re right about the ceremony. DDD has a reputation for being over-engineered. But look at Kai’s PR. Could you review it?”
“No.”
“Could you be confident it wouldn’t break billing?”
“No.”
“That’s the problem DDD solves at your scale. Not coordinating a thousand developers. Being able to change one thing without breaking everything else.”
She shows him the numbers from the teams she’s coached through this. Average PR size before boundaries: 23 files. After: 9 files. Review time dropped by more than half.
Tom looks at the data. “Fine. But if I ever have to write a UML diagram, I’m quitting.”
“Deal.”
That evening, Tom sits in his home office after the kids are asleep. Three monitors, the framed print of his first merged PR, LEGOs on the floor. Sarah comes in with tea.
“You’re quiet tonight.”
“Charlotte wants to carve up the codebase. Draw boundaries.”
“Is she right?”
Tom looks at Kai’s 47-file diff, still open on his centre monitor. “Yeah. Probably. It’s just –” He picks up a LEGO brick. “I built this. All of it. And now someone’s telling me it needs walls.”
Sarah leans against the door frame. “You love making things. But you hate letting anyone help you make them. You’re like your dad.”
Tom’s jaw tightens. His father runs a construction company. Marco, Tom’s brother, works there. Every family dinner, Marco talks about the business and Tom’s dad listens like it matters.
“That’s not fair,” Tom says.
“It’s not a criticism. The codebase isn’t yours any more, Tom. It’s theirs. That’s what growing means.”
She leaves the tea and goes to bed. Tom stares at the monitor for another hour.
The boundaries that don’t stick
Two weeks later, Kai opens another PR for the gift activation flow — what happens when a recipient clicks the link, creates an account, starts receiving boxes. The PR touches three bounded contexts.
Tom says, to nobody in particular: “I told you it wasn’t that simple.”
Charlotte doesn’t defend the diagram. She studies the event flows. The gift activation genuinely requires coordination between subscriptions, billing, and fulfilment. The feature isn’t violating the boundaries — the boundaries were drawn in the wrong place.
“You’re right,” she says to Tom. “The boundaries I drew were a first hypothesis. Let’s redraw them.”
The Subscription and Billing contexts share too many events. They merge into a single “Commercial” context — subscriptions, billing, gifts, pausing. Supply Matching and Fulfilment stay separate.
Tom watches Charlotte erase her own lines and draw new ones. He’d expected her to defend the original design.
“DDD is iterative,” Charlotte says. “The first set of boundaries is always wrong. You find out where by building against them. Kai’s PR told us something about the domain that the workshop didn’t.”
Kai looks at the new map. “So the 47-file PR was useful after all.”
Charlotte smiles. “The most expensive domain discovery session Greenbox ever ran. But yes.”
Making it real
The team refactors incrementally — Charlotte is adamant about no big-bang rewrites. They start with Billing (already somewhat isolated because of the Stripe API), then Supply Matching, then Fulfilment. Three weeks. Not perfect — some leaky abstractions remain — but the major boundaries are drawn.
New joiners can now be pointed at a single context: “You’re working on Supply Matching. Here’s the package. Here are the events. You don’t need to understand Billing to be productive.” Onboarding drops from two weeks to days.
A month later, when Maya announces a corporate catering service — weekly fruit boxes for offices — the bounded contexts prove their worth. Each context changes independently. Nobody’s PR touches 47 files.
The database needs splitting too. Tom’s first migration takes the site down for twenty minutes on a Sunday. Three subscribers email Sam. Tom resolves to learn zero-downtime migrations. The next migration, weeks later, goes live without anyone noticing. Progress.
What comes next
The boundaries are drawn. The team agrees on the contexts. But the wall is about to be painted over, and new joiners can’t read the sticky notes from three cities away. Next: turning the wall into living diagrams.
The next chapter, Drawing the System: From Event Storm to C4, publishes around 16 Jun.