Introduction
Structured Clarity: A Series on Architectural Integrity
- Part 1: Complexity, Coupling, and the Cost of Lost Clarity
- Part 2: Distribution is Not a Strategy for Clarity
- Part 3: Structural Dogma and the Illusion of Clarity (Current)
We spend massive amounts of energy debating the boundaries between systems, but we rarely apply that same scrutiny to the boundaries within them. In the previous article, we established the macro-service as the pragmatic boundary for distributed systems. We drew a hard shell around a cohesive set of responsibilities to protect the locality of reasoning from the friction of the network.
But drawing a boundary around a system does not automatically create order inside it.
If you look inside most enterprise monoliths, you will find a different kind of complexity quietly destroying a team’s ability to move safely. The industry default for organizing code is horizontal. We slice systems into technical strata: controllers, business logic, and data access. We often do this under the banner of N-tier design or “Clean Architecture”. On a whiteboard, these neat horizontal stacks look like the definition of discipline. They provide the illusion of clarity.
In practice, this is a structural trap. Horizontal layering fundamentally violates the principles of modularity. It forces a single business use case to be shattered across arbitrary technical boundaries. To understand or change one feature, an engineer must traverse multiple files across multiple layers. Context is scattered across the codebase. Worse, this structure encourages the dogmatic pursuit of DRY (Don’t Repeat Yourself), leading to shared services that quietly entangle completely unrelated features.
Modularity fails when boundaries are drawn around technical categories rather than business intent. To truly restore internal clarity, we must structure our code around the flow of change using vertical slices. Because enterprise reality dictates that we cannot simply replace our legacy systems wholesale from the outside, we must master the mechanical reality of refactoring the monolith from the inside out.
The Dissolution of Intent in Technical Strata

Grouping by Function, Fracturing by Intent
For decades, the industry’s default answer to organizing a growing codebase has been to slice it horizontally. We separate the presentation from the business logic, and the business logic from the data access. Whether you call it N-Tier, Onion, Clean, or even Hexagonal architecture, the practical outcome is often the same: we categorize code by what it is rather than what it does.
Categorizing code by its technical function guarantees that every business change will shatter across multiple layers.
To be fair, these patterns have noble intentions. Hexagonal architecture, for instance, is a brilliant methodology for isolating domain logic from infrastructure. In the hands of a highly disciplined team, it can work exceptionally well. But that is exactly the catch. It requires relentless, unyielding discipline. Because these patterns do not explicitly dictate how to organize the internal features, human nature takes over. We put all the interfaces in a “Ports” folder, all the implementations in an “Adapters” folder, and all the logic in a “Domain” folder.
On a diagram, this looks like the definition of order. If you look at the system from a distance, the boundaries are drawn cleanly across the stack, promising that changes to the database will not infect the user interface.
But software does not execute in horizontal bands, and it certainly does not evolve that way.
When a new business requirement arrives, it rarely asks you to simply modify a controller or just update a repository. The flow of change is vertical. A single feature pierces straight through those horizontal strata. Because traditional layered architecture fragments this logic across horizontal boundaries, it fundamentally undermines vertical cohesion. To understand how a single use-case works, an engineer must open a controller, trace it down to a service, follow it into a repository, and hold all of those disparate files in their head at once just to piece together the actual behavior.
What happens over time is predictable. Without extreme discipline, our business layers become a gravitational center for shared logic. An InvoiceService might start out handling one specific billing rule. A year later, because it is the designated place for invoice logic, it handles fifty. This gravity encourages bloated layers and implicit coupling across completely unrelated features.
We introduced these layers to isolate complexity, but we did not eliminate the coordination. We just displaced it, burying the actual intent of the system beneath a rigid technical taxonomy. Because the intent is buried, navigation becomes a cognitive tax.
The Cognitive Tax of Fragmented Code
When you open a repository organized strictly by technical layers, you are forced to navigate the system by category rather than by capability. If you need to understand how a user submits a time entry, you cannot just open a “Time Entry” module. Instead, you go on a scavenger hunt.
When structure reduces the context a developer must hold in their head, complex changes become safe, local decisions.
You start in a controller to see the entry point. You jump to a separate project to find the request DTO. You navigate to a business layer to read the service implementation, only to find it relies on an interface defined in a completely different core library. Finally, you dig into the infrastructure layer to see how the repository maps that state to the database.
The code is cleanly separated by technical concern, but the business context is completely shattered.
The real cost of change is measured in how far understanding must travel. In a horizontally layered system, knowledge is forced to travel much too far to get work done. The reference direction in these architectures is strictly top to bottom. This forces concepts that belong together, and ultimately change together, to be split across arbitrary technical strata.
Humans have limited working memory. If an engineer has to hold five different files in their head across three different projects just to validate a single business rule, their cognitive load maxes out on navigation rather than problem solving. The structure is no longer supporting their ability to reason. The architecture itself has become a cognitive tax.
When context is scattered, cognitive load spikes. The system forces you to absorb the entire stack just to make a safe, localized change. This forced mental mapping is the true source of accidental complexity.
The Danger of Dogmatic DRY
When navigation is painful, developers naturally try to minimize it by reusing existing paths. If all business logic lives in a single folder, it is tempting to share that logic whenever two features look vaguely similar. This is where the dogma of DRY (Don’t Repeat Yourself) transforms from a useful heuristic into a structural liability.
Reusing code across unrelated features trades a minor convenience today for permanent, brittle coupling tomorrow.
Consider the aforementioned shared InvoiceService. Two distinct features need to modify an invoice: a routine customer payment and a complex legal compliance adjustment. Because the architecture dictates that all invoice logic belongs in this service, both features are routed through the same shared methods. At first, this feels efficient. You avoided duplicating code. But months later, the compliance adjustment requires a new validation rule. A developer updates the shared service. The code compiles, local tests pass, but the customer payment feature quietly breaks in production.
The two features had absolutely no business relationship. They changed for different reasons and served completely different actors. Yet, they were structurally bound together simply because they were forced through the same shared service. By prioritizing technical reuse over business isolation, horizontal layers destroy the very autonomy they were meant to protect. We are left with a system where no change is truly local, and every modification requires defensive, global coordination.
Organizing by Intent

Aligning Structure with the Flow of Change
If horizontal layers optimize for the wrong kind of reuse, the pragmatic alternative is to optimize for the flow of change. Software does not evolve in technical strata. It evolves behaviorally. When a business requirement shifts, it usually affects a specific use-case, such as submitting a time entry or applying a payment.
True modularity encloses everything required to execute a single intent, from the entry point down to the state change.
To restore the locality of reasoning, we must group code by what it actually does. Several years ago, software architect Jimmy Bogard articulated the exact structural cure for this pain when he formalized the concept of Vertical Slice Architecture. Recognizing that traditional layered architectures force engineers to touch many different technical layers just to deliver a single feature, Bogard argued for a fundamental pivot: instead of coupling across an arbitrary technical layer, we must “couple along the axis of change.”
Rather than scattering a feature across a controller, a service, and a repository, we isolate its entire execution path. From the moment a request enters the system to the moment it alters state, everything required to execute that specific intent lives together in one cohesive unit.
When you organize by intent, a profound shift happens. The need for massive, shared abstractions simply melts away. As Bogard notes, the goal is to “[m]inimize coupling between slices, and maximize coupling in a slice.” You no longer need a generic InvoiceService to handle fifty different business rules. The code that processes a compliance adjustment only contains the logic for a compliance adjustment. It does not need to accommodate or navigate around the customer payment flow.
By aligning the structure of the code with the axis of change, we eliminate the scavenger hunt. The context you need to safely modify a feature is exactly where you are looking. When a new feature is needed, you simply add new code rather than modifying shared layers and hoping you have not triggered a side effect elsewhere. You are no longer fighting the architecture to understand the system.
Separating Intent and Tolerating Duplication
To actually build a vertical slice, you need a mechanism that enforces the boundary. In my experience, the sharpest tool for carving out these slices is Command Query Separation (CQS).
Intentional duplication of data shapes is the defensive firewall that prevents accidental coupling down the road.
It is important to separate CQS from its heavier architectural cousin, CQRS (Command Query Responsibility Segregation). When people hear CQRS, they immediately picture complex, event-sourced systems with completely separate read and write databases. You do not need distributed infrastructure to get the structural benefits of this pattern. While we reject the distributed overhead of heavy CQRS for a simple slice, we fully embrace the separation of read and write models within the monolith. Applying this separation at the feature level is a boundary-drawing exercise that divides how the system interacts with the world into distinct, isolated buckets of intent.
The fundamental rule is that an operation should either be a Command that mutates state, or a Query that returns state, but never both. While both represent an explicit intent to act, I also treat Events as a third peer in this model to represent a reaction to a prior fact. You can build vertical slices without these exact patterns, but organizing around Commands, Queries, and Events gives you a naming and structural discipline that makes the boundaries hard to ignore. Each fulfills a distinct use case, and each demands its own isolated vertical slice.
This division provides strict mechanical boundaries. A Query responsible for displaying an invoice on a dashboard does not share a data model with the Command responsible for applying a payment to that invoice. Likewise, the Event that triggers an email notification when that payment succeeds lives in its own isolated lane. Each slice owns the exact data shape it needs to fulfill its specific purpose.
To maintain this isolation, however, you have to commit a bit of software heresy. You must explicitly reject dogmatic DRY (Don’t Repeat Yourself).
In a traditional layered system, developers often rely on shared data contracts. The ubiquitous and terribly named InvoiceDto is a classic example. Because it is named for its technical type rather than its business intent, it becomes a magnet for reuse. A developer will inevitably wire the payment Command, the compliance Query, and the notification Event handler to use that exact same object.
This is where we must draw a hard line between two very different types of duplication. Duplicating domain logic (the actual, authoritative business rules of the system) is dangerous. But duplicating data shapes across architectural boundaries is essential.
If we share that InvoiceDto today, we create a hidden dependency. Six months from now, the Query might need to return a newly added tax calculation field. If the shape is shared, the Command and the Event are now forced to accommodate a change they do not care about. By intentionally duplicating the shape so that each slice retains total ownership of its inputs and outputs, we prevent accidental coupling tomorrow. We deliberately choose duplication over the wrong abstraction, ensuring that our mechanical isolation remains intact as the system evolves.
Collapsing the Operational Blast Radius
When we enforce mechanical isolation through Commands, Queries, and Events, we fundamentally alter the risk profile of the system. In a highly coupled horizontal architecture, different parts of the codebase are interwoven. This means a simple bug fix in a shared service can have unintended consequences elsewhere, introducing significant risk for even minor modifications. The blast radius of a change is effectively global.
When a feature shares no logic and no data shapes with its neighbors, a local defect remains a local problem.
Vertical slicing collapses that blast radius. If a defect surfaces in the logic for applying a customer payment, the fix is confined entirely to the handler for that specific command. It is highly unlikely to ripple into the compliance adjustment query because they do not share business logic, and they do not share data shapes.
I have spent my career watching horizontal architectures collapse under the weight of their own coordination. That scar tissue has shaped my baseline. Organizing systems via vertical slices is not a theoretical ideal. When enforced by the strict separation of intent, it is the most reliable way I have found to develop software that survives continuous evolution. It forces the codebase to become a direct map of actual business capabilities.
This operational containment is exactly what makes safe iteration possible. When business logic is no longer spread throughout various parts of the system, developers are not forced to hold the entire architecture in their heads just to do their jobs safely. You only need to load the single vertical plane you are currently modifying into your working memory.
By narrowing the scope of understanding required to make a change, we eliminate the hesitation that plagues legacy systems. Development cycles accelerate, and defect rates drop. We have stopped forcing the developer to carry the complexity of the system in their mind. We have put that burden back on the structure itself, right where it belongs.
The Inside-Out Strangler Fig

The Trap of the Perimeter Rewrite
When an enterprise system becomes too painful to change, the industry usually reaches for the Strangler Fig pattern. This analogy, famously introduced by Martin Fowler, is beautiful in its precision. It mirrors the biological process of a vine overtaking a tree, where new systems grow around and eventually replace legacy ones. In software, this typically involves building a brand-new application on the perimeter and slowly routing execution to the new code over time.
A legacy monolith is a permanent resident of your architecture. You do not outrun it; you restructure it from within.
But there is a fundamental flaw in how this pattern is almost always applied.
Treating the Strangler Fig as an “outside-in” process is often just a prolonged rewrite in disguise. You are not rescuing the legacy system or improving its structural clarity. You are simply drawing a new boundary around the outside and leaving the tangled, highly coupled core completely untouched. You build a replacement next door and hope you can finish it before the business loses patience. Because the new system sits on the perimeter, it eventually has to reconcile with the entangled data and shared logic of the old system. That reconciliation is exactly where these modernization efforts stall and fail.
This approach relies on a critical misconception about the plant it is named after. We assume the host tree will quickly rot away under the weight of the vine. In nature, however, the original tree does not always die or disappear. The new and old systems coexist in a mutualistic relationship, even if just for a time.
The exact same thing is true in enterprise software. A legacy monolith has gravity. It processes the actual revenue, holds the actual state, and supports key operations that will remain foundational for a long time. It is going to survive the modernization effort.
We must stop treating the monolith as a sinking ship we need to escape. If we accept that the legacy core is a permanent resident of our architecture, our goal shifts. The objective instead is to forge a cohesive macro-service. A macro-service protects cognitive boundaries and is the pragmatic destination, not a layover. The legacy system, properly refactored, becomes the permanent structural core of that macro-service. If the monolith is here to stay, we cannot simply wrap it from the outside and wait for it to die. We have to change our relationship with it, and we have to transform it from within.
Ruthlessly Enclosing the Data
To strangle a monolith from within, you do not build a new application. You build a new boundary.
If a new vertical slice does not exclusively own its database schema, the structural boundary is merely an illusion.
This is the move most modernization efforts miss. The process must be highly surgical. You identify a single use case, such as a specific Command, Query, or Event. You trace its logic from the entry point all the way down through the horizontal layers, and you physically lift and shift that logic into a new, isolated location within the existing codebase. The vine works from within, spreading throughout the core of the tree rather than simply wrapping it from the outside.
Once this vertical slice is established, any legacy code that relies on this behavior must be rerouted to use the new slice’s explicit entry point. This is where the structural discipline becomes absolute. Nothing else in the system is allowed to access the handler’s internal logic. There is no reaching under the hood to borrow a validation method. There is no instantiating the slice’s data access code from a legacy service. You go through the front door, or you do not go in at all. This is our resistance to dogmatic DRY in practice. We tolerate duplication to ensure the slice remains sovereign and impenetrable.
However, isolating the code is only half the battle. The dark horse of any modernization effort is data entanglement.
You can draw perfect boundaries in your application tier, but if the data remains entangled, you are solving almost nothing. Separating the code while leaving the data intertwined does not distribute the system. It just makes the coupling harder to see. The database is the lynchpin. Isolating data within a monolith means carving out dedicated tables or even separate database schemas for the vertical slice. Untangling foreign keys and shared relational views is mechanically painful but absolutely necessary. If the slice owns the logic, it must exclusively own the schema that supports it. If the legacy system continues to mutate that exact same data through the old, shared horizontal layers, your new boundary is an illusion. The slice must enclose its data just as ruthlessly as it encloses its logic.
Making Boundaries a Structural Reality
If a boundary relies on developer discipline to survive a deadline, it will eventually fail. Under the pressure of a looming release, human nature naturally drifts toward the path of least resistance; if a clean new repository is left public, someone will eventually reference it from a legacy service just to get the job done. To prevent this erosion, we must transform our boundaries into a mechanical reality of the build process.
To preserve the locality of reasoning, we must move the burden of system integrity from the developer’s memory to the mechanical certainty of the build process.
In the .NET ecosystem, this starts with physical assembly separation. Logical folders within a single project offer the illusion of order but provide no protection against lateral bleed. By extracting a module into its own physical assembly, we allow the internal keyword to function as a clear glass firewall. Implementation details like data access logic and domain models remain invisible to the legacy monolith, as the compiler itself guards the gates, rejecting any attempt to take a forbidden dependency.
We can deepen this defense through structural encapsulation using private nested classes. While combining a complex slice into a single file can become onerous, C# allows us to use partial classes to maintain organizational clarity. We can spread a vertical slice across multiple files, keeping the public interface in one and the private, encapsulated implementation in another. This ensures the “how” of a feature is physically unreachable from the outside, even for other classes within the same assembly. It enforces a strict locality of reasoning where only the explicit public contract is exposed to the rest of the system.
Finally, we must acknowledge that even the compiler has limits. To catch coupling that might slip through the cracks, we employ architecture unit tests. These act as automated auditors that validate the integrity of our blueprint with every build. A test can explicitly assert that no class in the “Invoicing” module is permitted to reference implementation details in the “Documents” module. If a developer attempts to bypass a boundary, the tests fail.
By moving from “best practices” to mechanical enforcement, we stop forcing the developer to carry the system’s complexity in their mind. We put that burden back on the infrastructure of the codebase, transforming a conceptual design into the lived, breathing reality of structured clarity.
Conclusion

We build software for systems that have to last. The horizontal layers we have relied on for decades optimize for technical categorization rather than the flow of change. They offer an illusion of order, but without proper structure, that clarity quickly erodes. Over time, those layers quietly turn our systems into entangled knots that become difficult to trust and dangerous to modify.
To make a system comprehensible, we must organize our code strictly around intent. By building vertical slices through Commands, Queries, and Events, we ensure that things that change together stay together. We accept the duplication of data shapes to protect the isolation of business rules. We stop trying to outrun the legacy monolith with outside-in rewrites. Instead, we use an inside-out refactor to forge the macro-service boundaries we defined in our previous discussions. We carve out clean modules, isolate the data, and shift the burden of system integrity from human memory to the mechanical certainty of the build process.
The illusion of layers will always collapse under the pressure of continuous change. Clarity in these systems is never accidental; it is earned through deliberate design. When that design is driven by intent and secured by structural encapsulation and automated audits, the result is structured clarity.
Architecture is not a conceptual boundary drawn on a whiteboard; it is the mechanical reality of change.
All content is based on my own research, learnings and real-world implementation experience. Visuals are custom 3D assets built and rendered in Blender. Read more about my craft here.
