Have you ever thought about the interaction between railroads and the buildings they pass by? The question sounds foolish at first — how could a train interact with a building? They’re completely isolated. The train has a track that keeps it moving in one specific path. The building can’t move at all. So how do they interact?

A recent story out of Portland shows they in fact, do. A railway track was closed to all traffic to stop it from knocking over a building. You read that correctly — the track was closed because authorities were concerned the train would knock over the building. How could that happen? We already said the tracks keep the train on a single course. And there are rules that govern space next to railway. It’s not just a “don’t knock the warehouse over.” There are employee safety setbacks from the tracks and zoning setbacks that easily put enough space between the tracks and the neighboring structures to prevent any “accidental” collision. So how would a train knock over a building?

The answer requires us to reframe how we think about the relationship between the train and the building entirely. Up to this point, we’ve assumed a train would need to collide with a building to knock it over. We haven’t considered vibrations. Trains are extremely heavy. When they travel, the weight causes vibrations in the ground. Those vibrations travel through the ground and enter nearby structures. Under normal circumstances, the structures just absorb the vibrations. It’s not even that “they’re built for it” per se. There’s just no real concern that a railroad would damage a building.

“It is extremely rare for vibration from train operations to cause substantial or even minor cosmetic building damage.”

FTA Report 0123 p.126

This wasn’t normal circumstances. The warehouse was old and abandoned. Centennial Mills built it, but when they shut down their milling operations in Portland in 2000, they sold the property to the city. Fast-forward to August 2026. In its state of disuse, a large fire broke out and burned the warehouse into a hollow shell. No one was hurt, but the building was structurally compromised.

And that brings us back to our vibrations. The vibrations from the train had always been there. The train had been running next to that warehouse for years. No one had ever asked “can this warehouse really handle these vibrations,” because, like we said, there’s basically no chance of a railroad affecting surrounding structures. So when the city informed the railroad that they could no longer operate due to the risk of those exact vibrations, they must have been met with a bit of shock.

There’s a design question at the core of all of this, and it boils down to asking whose fault it was that the rail service had to stop. The fire caused a change in the warehouse, but the railroad was the one that was affected. That might feel unfair. After all, the railroad did nothing wrong. Before we jump to that conclusion and blame the city, let’s consider the situation from the perspective of system design.

The first first thing to note is the railroad and the warehouse exist together in a single system. A system doesn’t end with ownership. It includes every component that actually interacts in the system. In this case, the train and building were part of a system precisely because they interacted. Even though it was unspoken, the building had a job in that system: absorb vibrations from the railroad. That means, and this is the critical insight for system design, the railroad was depending on the warehouse.

One system: trains, ground, warehouse

We call it a dependency because when it changes, it can cause the component with the dependency to break. That’s exactly what happened here. The fire wiped out the warehouse’s structural ability to withstand vibration. That violated the unwritten dependency contract with the railroad, and suddenly, the railroad stopped working. These felt like two unrelated systems until we found the dependency: vibration through the ground. What’s worse, this was actually an invisible dependency. By invisible, we mean the warehouse has no way of knowing the railroad is depending on it. It wasn’t until the warehouse changed that the dependency became obvious… and led to the misplaced blame.

When reminders watch the status column

Let’s consider how this invisible dependency shows up in software. This story is made up, but fair warning, it might feel a bit too familiar if you’ve worked with a long-lived product before.

The system starts with a sound design. It’s a simple task list with no fluff. It just needs: users and tasks. Status on a task is a check-off, it’s done or not done. The whole product is user-action driven. They literally click a button in the UI for creating, editing, checking off, deleting, etc. That makes our architecture dead simple: spin up a single HTTP server for everything. No cron. No background poll. No automation module in the original plan.

Later, we expand the design to include a due_date on tasks. We’ll couple that with a new preferences column on the users table for how many hours before due the user wants a reminder. Then, we’ll do some software magic to actually send reminders based on due dates and the configured reminder window.

Here’s where the trouble starts. The new reminder feature has a fundamentally different operational model than the existing system. It needs to run on a schedule. We typically call this a “cron” in the industry. It’s a mechanism that runs a block of code on a timer. So we stand up a cron-style service, give it a query for “tasks that need a reminder,” and have it send notifications.

Task list HTTP server versus cron-style reminder service components

The invisible dependency shows up when we expand the reminder feature. They want to gamify task completion. When a user finishes a task before the deadline, there’s a chance they’ll get a coupon code. The reminder system feels perfect for this. It’s already a cron job and already has access to the data. We just need the reminder system to be notified when a user completes a task.

There’s a shortcut to make this notification work. Have the reminder service watch the task status column (poll it or attach a CDC-style feed to it). When the value moves, act. Many teams have fallen for this shortcut, because it feels so useful and innocuous. It’s functionally a notification and it requires very little code change to make it work. In fact, the team that owns reminders can implement this entirely on their own! There’s no need to coordinate with the team that owns tasks to implement an event bus or publish certain types of events or handle access.

The problem is, this breaks the contract. Status was never designed as an event channel. It was a private data point to the task system. Subscribing to changes creates the exact same type of invisible dependency we saw with our train story. As long as status keeps the same name, and the same meaning, and the same possible values, the system works.

That meaning could stay the same for years before it causes a problem. But one day, when the task status changes meanings, the reminder system will break. And we’ll have the same blame game we saw at the rail yard. Tasks changed, reminders broke, the task team gets blamed.

In software architecture, we design for exactly this type of problem. If events are important as a signal, we design an event bus. Sure, today that means “the status column changed,” but we intentionally make a separate bus for the messages. It’s important to note that the new bus increases system complexity. There’s more to code, more to deploy, and more to manage. We’re not adding it to make things simpler today — we’re adding it to explicitly capture the dependency that exists between the systems. We want “task status change” to be a permanent concept regardless of how tasks evolve.

The Blame Game

Invisible dependencies lead to blame games. The reminder team blames the task team: you changed the database; you broke us. The rail company blames the city: “you let the warehouse burn down, you broke us.”

From the outside, that’s almost fair. A change landed. Behavior broke. But the coupling was created the day someone treated an invisible dependency, like a silent status-column watch, or an adjacent structure’s integrity, as a legitimate promise. The task team didn’t owe that cron job a frozen meaning. The reminder team built the dependency and then acted surprised when the other side evolved.

The party that feels broken is the one that didn’t ship a change. That doesn’t tell you who created the coupling.

Feeling broken vs creating the coupling
Feeling broken is not the same as who created the coupling.

The coupling was created the moment tracks and building got close enough that ordinary rail vibration and structural integrity were tied together. Maybe the building came first. Maybe the railroad did. I don’t know, and I’m not going to pretend I do. The dependency doesn’t care about the groundbreaking dates. Proximity made two systems share a fate.

Same as the reminder service: the failure shows up as “they changed something,” but the architectural fact is “we were coupled and we never owned that coupling.”

Ultimately, in system design, being broken doesn’t make you the victim. Trace the dependencies and changes. If it was an invisible dependency, the team actually broke themselves! The way to avoid this is explicit dependencies with dedicated systems that let control how systems interact. Teams can own the contracts and the change cycle. They can safely deploy without worrying “who might be depending on this value.” Name it. Own it. Document it. If you don’t, you end up with a web of invisible dependencies you can’t really understand until they break.

There’s a pattern for that.


Leave a Reply

Your email address will not be published. Required fields are marked *