← Back to list

Every Change Took 2 Days — Until We Fixed Our Module Boundaries

A real-world story of how broken module boundaries slowed our team to a crawl — and how redesigning them unlocked speed, clarity, and…

Things I Broke in Production · 2026-01-11 07:20 · 0 claps · 5.1 min read
#software-architecture #modular-design #microservices #backend-development #engineering-productivity
Open on Medium ↗
Wiki topics: 🌐 · Web Development ⏱️ · Productivity 🧠 · Mental Wellness 🏛️ · Architecture

Every Change Took 2 Days — Until We Fixed Our Module Boundaries

A real-world story of how broken module boundaries slowed our team to a crawl — and how redesigning them unlocked speed, clarity, and sanity.

The worst part wasn’t the bugs.

It was the waiting.

Every time I changed a line of code, I knew what was coming: two days of tests, approvals, unexpected failures, Slack pings, and someone somewhere saying, “Hey, why did this break?”

Two days. For a simple change.

That’s when I realized something uncomfortable — the problem wasn’t our developers. It wasn’t even our tools.

It was our module boundaries.

The Team That Couldn’t Move

I was working on a fairly typical enterprise system. Think Spring Boot services, APIs talking to each other, databases with just enough normalization to be dangerous. We weren’t juniors. We had smart people, decent CI, and years of production experience.

But our delivery speed was… embarrassing.

A simple change like:

“Add one field to the customer response”

…would ripple through half the system.

One API changed → Another service failed → A UI test broke → Someone hotfixed something → And now QA had questions.

It felt like touching one brick made the whole building wobble.

And yet, when you asked anyone, “Why is it so slow?” you’d get vague answers:

  • “Legacy code”
  • “Too many dependencies”
  • “Microservices are hard”

None of those were wrong. But they weren’t useful either.

The real problem was deeper.

We Didn’t Have Modules. We Had a Tangle.

On paper, our system was “modular.”

We had:

  • customer-service
  • loan-service
  • auth-service
  • notification-service

Nice names. Very clean.

In reality?

They were emotionally codependent.

The customer service knew the database schema of the loan service. The loan service directly called internal methods of auth. Notification logic was scattered across three services.

If you drew a dependency graph, it looked less like clean architecture and more like spaghetti dropped on a plate.

So every change became dangerous. You weren’t editing a module — you were poking a web.

And everyone was scared to move.

The Hidden Cost of Bad Boundaries

Bad module boundaries don’t scream. They whisper… every single day.

They show up as:

  • PRs that are way too big
  • Tests that fail “randomly”
  • Developers who avoid touching certain files
  • Release notes filled with “minor changes” that took a week

We were living that reality.

I remember a change where I just wanted to tweak validation logic for a loan application. It should’ve taken 20 minutes.

Instead:

  1. I had to update a shared DTO.
  2. That broke three consumers.
  3. One of them was owned by another team.
  4. Their release cycle was next week.

So my 20-minute change waited six days.

That’s when it hit me:

Our system wasn’t slow because of technology. It was slow because of how we sliced responsibility.

What Are Module Boundaries, Really?

Here’s the thing most teams miss.

A module boundary isn’t about folders. It’s not about packages. It’s not even about services.

It’s about who owns what decisions.

A good module should answer:

  • What does this part of the system know?
  • What does it not know?
  • What is allowed to change without asking permission?

In our system, everything knew everything. Which meant nothing was safe to change.

We had “microservices”… but no autonomy.

The Day We Finally Looked at the Diagram

One afternoon, after yet another blocked deployment, a few of us gathered in a meeting room with a whiteboard.

No Jira tickets. No deadlines.

Just one question:

“If this system were designed today, how would we actually divide it?”

We started drawing boxes.

Not by database. Not by API. But by business responsibility.

We wrote things like:

  • Customer identity
  • Loan eligibility
  • Payment processing
  • Risk assessment
  • Notifications

Then something interesting happened.

Those boxes didn’t line up with our existing services.

Not even close.

The Truth We Didn’t Want to Admit

We had built modules around technical convenience, not around business meaning.

For example:

  • “Customer Service” handled everything from profile updates to credit scores.
  • “Loan Service” knew about UI validation rules.
  • “Notification Service” decided business workflows.

No wonder everything was tangled.

We weren’t modeling the business. We were modeling our past decisions.

The Rule That Changed Everything

We introduced one simple rule:

A module is allowed to change its internal implementation freely — but nobody else is allowed to depend on it.

That meant:

  • No other service could reach into your database
  • No other module could import your internal classes
  • No “just this one shortcut”

If someone needed something from your module, you gave them an API, not your internals.

It felt annoying at first. Slower.

Ironically, it made us faster within weeks.

The First Boundary We Fixed

We started small.

Our “Customer” logic was everywhere. So we carved out one clean module:

Customer Core

Its job:

  • Own customer identity
  • Own customer state
  • Expose APIs for reading and updating customers

Everything else — loans, notifications, risk — had to go through it.

No direct database access. No shared entities. No shortcuts.

Just contracts.

The first PR was painful. The second was easier. By the fifth, something felt different.

Changes Started to Feel… Boring

That’s when I noticed it.

A teammate added a new customer attribute. No one panicked.

Tests ran. Nothing broke.

No cross-team Slack messages. No emergency calls.

Just a merge.

It was so boring that we almost didn’t celebrate.

But boring was the dream.

Why This Worked (And Usually Doesn’t)

We didn’t just create modules.

We created trust.

When a team knows:

  • “This module won’t surprise me”
  • “Changes won’t leak out”
  • “APIs are stable”

They move faster.

Bad boundaries force coordination. Good boundaries remove it.

And coordination is where time goes to die.

The Real Metric: Cognitive Load

Before, any change meant:

“Let me check who else this might break…”

Afterwards, it became:

“This is inside my module. I’m safe.”

That shift — from fear to focus — is priceless.

You don’t need fewer bugs. You need fewer things to worry about.

Practical Ways to Fix Your Boundaries

If you’re in a messy codebase (and let’s be honest, you are), start here:

1. Draw the business, not the services

Map out real business responsibilities. Compare them to your code. The gaps are where pain lives.

2. Kill shared models

Shared DTOs feel efficient. They are actually coupling in disguise.

3. Make illegal access impossible

Use package boundaries, build rules, or even separate repos if needed.

4. Prefer APIs over imports

If another module needs your data, give them a contract — not a class.

5. Start with one module

Don’t boil the ocean. Fix one boundary. Feel the difference. Then continue.

It Was Never About Speed

Here’s the quiet truth.

We didn’t become faster because our builds improved. We became faster because we stopped stepping on each other’s toes.

Two-day changes turned into two-hour changes not because we wrote more code — but because we wrote less tangled code.

That’s what good module boundaries give you:

Space to breathe. Room to think. And the freedom to move.

Final Thought

If every change in your system feels scary, it’s not because you’re bad at coding.

It’s because your architecture doesn’t trust you.

Fix the boundaries — and suddenly, the whole system feels lighter.

If this story hit close to home…

Follow me on Medium. I write about real-world backend systems, Spring Boot, and the painful, fascinating things that only show up in production.

And if you’ve ever fought a codebase that refused to move — you’re not alone.


메타데이터
post_id
405ebd3d7746
slug
every-change-took-2-days-until-we-fixed-our-module-boundaries-405ebd3d7746
url
https://medium.com/@aniruddhasonawane/every-change-took-2-days-until-we-fixed-our-module-boundaries-405ebd3d7746
canonical_url
https://medium.com/@aniruddhasonawane/every-change-took-2-days-until-we-fixed-our-module-boundaries-405ebd3d7746
author_url
https://medium.com/@aniruddhasonawane
status
ok
fetched_at
2026-06-09 15:37:30