Modern software architecture is full of metaphors—and none may be as visually striking as the Onion Architecture. But what does it really mean when we talk about “layers,” “abstractions,” and “interfaces”? In this article, we’ll unravel these concepts and explore the foundational principles that shape robust, flexible systems.
Peeling Back the Onion
The Onion Architecture represents the structure of a system as concentric circles. At the core are business rules and domain logic—the heart of your application. Each successive outer layer adds more concrete concerns: application services, then infrastructure details like databases and web APIs.
But there’s more here than just organization. The architecture encodes a philosophy about the direction of dependencies:
- Inner layers define abstractions and business logic.
- Outer layers deal with technology-specific details, such as data storage, messaging frameworks, or UI.
The Dependency Inversion Principle
Central to Onion Architecture is the Dependency Inversion Principle (DIP):
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Here, high-level means “more abstract,” not “more external.” In other words, the innermost circles of the onion (business logic) are the most abstract—they define the rules; they don’t know or care how those rules interact with a database or a web API.
The outer circles—concerned with frameworks or services—are low-level, dealing with the concrete implementation of these abstractions.
Layers and Interfaces: How Communication Really Works

So, how do various architectural layers talk to each other? Let’s break it down:
- Inner layers define what’s needed (interfaces or contracts).
For example, your core logic might need to save data but shouldn’t directly interact with a database. Instead, it defines an abstraction (an interface) likeIDataStore. - Outer layers implement those contracts.
The infrastructure layer provides a class likeSqlDataStorethat implementsIDataStoreand connects to the actual database. - Abstraction is injected at runtime (Dependency Injection).
The core logic uses only the interface, and the specific implementation is provided externally (often with a Dependency Injection framework).
Direct Inward Access—And Its Limits
It’s perfectly fine—and expected—for outer layers to directly access inner layers. For example, an ASP.NET controller (outer) calls into a service (inner) to execute business logic. The dependency points inward, and the controller can use the inner service directly. However, the inner service should never reference nor know about the outer controller.
“Interface Is Owned By the Client”

A subtle yet important force at play here is the principle that “the interface is owned by the client.” That is, the class depending on an abstraction (not the implementation) should be the one to define it. This lets the client control its own needs, unconstrained by the provider’s design.
For instance:
- If an outer layer—say, a controller—wants to insulate itself from inner layer complexities, it defines an interface tailored to its own needs.
- The controller then only interacts with this interface, while proxies or wrappers bridge the call to actual inner layer classes.
- The inner classes never know they are being wrapped, nor do they implement outer layer abstractions—preserving the architectural boundaries and dependency direction.
Bringing It All Together
Through these discussions and thought experiments, we have arrived at a robust model:
- Onion Architecture enforces dependency direction (inward).
- The Dependency Inversion Principle clarifies what “high-level” and “low-level” mean—innermost layers are most abstract, outermost are most concrete.
- Abstractions (interfaces) are defined by the client and implemented by providers. This keeps layers loosely coupled, testable, and adaptable to change.
Final Thoughts
Next time you look at an architectural diagram with circles or layers, remember: abstraction isn’t about what’s “outside,” it’s about what’s “conceptual.” The onion, the layer, and the interface all work together, shaped by the need for decoupling, flexibility, and maintainability.
By honoring the forces of dependency, abstraction, and client-owned interfaces, we build software that is truly robust—from the core to the skin.
