Ever had one of those “just one small change” moments that ended in an avalanche of broken code? Yeah, me too. Once, I tried tweaking a tiny validation rule in a legacy system, only to find myself knee-deep in database queries, UI bugs, and business logic spaghetti. It was a nightmare. That’s when I realized: we needed Onion Architecture.
What’s the Deal with Onion Architecture?
Think of a well-structured codebase like an onion (minus the tears). Layers. Encapsulation. A clear separation of concerns. Onion Architecture, popularized by Jeffrey Palermo, is all about keeping your business logic independent of external dependencies.
The Layers (Peeling It Back)
-
Core (Domain Model) – This is the heart of the application. It contains entities, value objects, and domain rules.
- No database code, no frameworks - just pure business logic.
- Example: In a rental platform, this layer would define
Property
,Reservation
, andRentier
as entities, along with the business rules that govern them.
-
Application Services (Use Cases) – This layer coordinates use cases and ensures business logic is executed properly.
- It doesn’t implement business rules but calls them when necessary.
- Example: A
ReservationService
ensures a property is available before allowing a booking.
-
Adapters (Infrastructure & UI) – These are gateways to the outside world.
- Databases, APIs, file systems, messaging systems, they all interact through this layer.
- Example: A repository pattern implementation that fetches
Reservations
from a database.
-
External Dependencies – The farthest layer. Things you don’t own, like ORMs, web frameworks, and third-party services.
- These should be interchangeable without affecting business logic.
- Example: Today, you might use PostgreSQL, but tomorrow you might switch to MongoDB without breaking core logic.
Each layer depends inward, never outward. The core doesn’t know about the database; it just defines the rules.
Why Bother?
1. Flexibility & Testability
Ever tried testing a function that’s tightly coupled to a database? Painful. With Onion, you can test business logic without worrying about infrastructure. No more “let’s just mock half the world to test this one thing.”
- Example: You can unit-test domain rules (
Reservation.canBeCancelled()
) without spinning up a database. - Result: Faster, more reliable tests and less flakiness.
2. Better Code Longevity
Frameworks come and go. Today’s cutting-edge ORM might be tomorrow’s outdated relic. By keeping core logic independent, you can swap out tools without rewriting everything.
- Example: Imagine switching from Flask to FastAPI in Python. With Onion, only the outer layer changes.
- Result: Your core logic remains intact, reducing migration headaches.
3. Scalability & Maintainability
When code is well-structured, teams can add features without breaking everything.
- Example: A new payment method? Just add an adapter in the outer layer—no changes to core business rules.
- Result: Clean separation means fewer side effects, making large systems manageable.
4. No More Accidental Spaghetti
It forces discipline. No more “just this once” shortcuts where you access the database directly from the UI.
- Example: A React component calling a SQL query? Nope. Instead, it calls an application service that enforces domain rules.
- Result: Code stays clean and structured, preventing technical debt.
But… Is It Worth It?
Not every project needs a full-on onion. If you’re building a small CRUD app, a simple layered approach might be enough. But if you’re dealing with complex business rules, long-term maintainability, or ever-changing tech stacks — this is a lifesaver.
Final Thoughts
Onion Architecture isn’t just about structure; it’s about mindset. Keeping dependencies at bay. Writing code that lasts. And avoiding those “just one small change” disasters. So next time you’re structuring an application, ask yourself: Does this change make my core more independent? Or am I baking in unnecessary dependencies?
Your future self (and your teammates) will thank you.