Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
Inverting Dependencies: Where Layered Architecture Gets Stuck
In a layered architecture compile-time dependencies flow downward, so business rules get locked to EF Core, SQLite and external APIs; the fix is to keep the runtime call direction but move the interfaces into the domain project that uses them, turning the reference arrow inward. Shown with Bookstore's "test without a database" and "switch carriers" scenarios, the "I added an interface, done" fallacy, and the idea shared by Hexagonal, Onion and Clean.
Hexagonal Architecture (Ports & Adapters): Isolating the Application from the Outside World
Hexagonal Architecture (Cockburn, 2005) keeps the application equally distant from UI, tests, batch scripts and the database: the outside world talks through purpose-based ports, and each technology plugs in through an adapter. In Bookstore we drive the IPlaceOrder driving port from a Minimal API, a console CSV import and an xUnit test, and back the IOrderStore driven port with EF Core and in-memory adapters; plus ASP.NET Core's IServer and common misreadings of the hexagon.
Onion Architecture: Domain Model at the Center, Infrastructure on the Outer Ring
Onion Architecture (Palermo, 2008) builds the application around an independent domain model: inner rings define interfaces, outer rings implement them, all coupling points toward the center, and the core can be compiled and run without infrastructure. We place Bookstore's ordering code into Domain Model, Domain Services and Application Services rings and show how it differs from layered and Hexagonal, how rings relate to projects, and the ASP.NET Core Identity example.
Clean Architecture: The Dependency Rule, Use Cases, and Data Crossing Boundaries
Clean Architecture (Martin, 2012) has one rule, the Dependency Rule: source code dependencies point only inward, and what crosses a boundary is a simple data structure shaped for the inner circle, not an entity or a database row. In Bookstore: the four circles, the JSON cycle error caused by returning an EF Core row, the output port and presenter versus the pragmatic ASP.NET Core counterpart of a handler returning a Result, and a comparison of Hexagonal, Onion and Clean.
The Application Layer and Use Cases: Handlers, Validation, and Transaction Boundaries Without MediatR
In the Application layer each use case is a single-purpose handler class: input validation stays in Web, business rules stay in the Domain, expected failures come back as a Result, and a single SaveChanges draws the transaction boundary. Adding cross-cutting concerns with Scrutor decorators instead of MediatR, and MediatR's licensing after 13.0.0.
Persistence Ignorance and Infrastructure Adapters: EF Core, HttpClient, and External Services
Persistence ignorance means domain classes don't know which database or ORM they are written to; with EF Core you keep the mapping in Infrastructure through IEntityTypeConfiguration, but rules such as constructor binding and getter-only properties still shape the domain. Wrapping the shipping company's API in a driven adapter with a typed HttpClient, keeping the external model out, and the generic repository and IQueryable trap.
Testing and Protecting Clean Architecture: Core Tests, Adapter Tests, and Architecture Tests
In Clean Architecture you test the core without a database using in-memory adapters, test the adapters against the production SQL Server with Testcontainers, and make the Dependency Rule unbreakable in CI with ArchUnitNET rules. The decimal precision problem SQLite hid, violations the compiler misses, the trap of a rule that selects nothing without Types(true), and a note that NetArchTest hasn't been updated since 2021.
When Is Clean Architecture Too Much? Template Cargo Cults, Interface Explosion, and a Pragmatic Path
Clean Architecture is too much for CRUD-heavy, short-lived or single-adapter small services with no business rules to protect, and pays off in long-lived regions with rules and several adapters. The symptoms of template cargo cults and interface explosion, protecting the same Dependency Rule with a single project and ArchUnitNET tests, and migrating a layered project gradually with FreezingArchRule.