Every .NET architect loop eventually asks about Clean Architecture, and most candidates can draw the concentric circles from memory. What separates a strong answer is knowing why the circles point the way they do, where the terminology of Onion and Hexagonal Architecture overlaps with Clean Architecture versus genuinely differs, and — more importantly — recognizing when the whole layering exercise is not worth its cost for the problem in front of you. This page works through the dependency rule, ports and adapters, where validation and business rules actually belong, and how these architectures evolve and sometimes over-engineer real .NET codebases.

Q1 State the Dependency Rule at the center of Clean Architecture precisely. What's actually being protected, and what breaks first when it's violated?#

Short answer: Source code dependencies may only point inward: outer rings, such as frameworks, databases and UI, can depend on inner rings, such as entities and use cases, but an inner ring must never reference anything in an outer ring — what this protects is the ability to test and reason about business rules in complete isolation from infrastructure, and to change infrastructure without touching business logic.

The first thing that breaks when this is violated is usually testability, not correctness — a domain entity that references an EF Core attribute like [Required] or a base class from Microsoft.AspNetCore.Mvc still compiles and runs, but now the "pure" business logic project has a package reference to a web framework or an ORM, and unit-testing that entity means the test project also has to reference and understand that infrastructure. The second thing that breaks is the ability to swap infrastructure: if use-case classes call DbContext directly instead of an interface the use case itself defines, moving from EF Core to a different persistence strategy means rewriting the use cases, not just writing a new adapter, which defeats the entire premise of the layering.

C#
// Violates the Dependency Rule: domain type directly referencing infrastructure.
public class Order
{
    [Required] // System.ComponentModel.DataAnnotations is fine; an EF Core-specific attribute is not.
    public string CustomerId { get; init; } = default!;
}

What interviewers look for: naming testability and infrastructure-swappability as the concrete payoffs, not just reciting "dependencies point inward" as an abstract rule.

Common mistakes: believing the Dependency Rule is only about namespaces or project references, and missing that a domain type using an infrastructure-specific attribute or base class is a violation even if it "still works."

Q2 Compare Clean Architecture, Onion Architecture and Hexagonal Architecture. Are they meaningfully different, or the same idea rebranded?#

Short answer: All three describe the same core idea — isolate business logic from frameworks and infrastructure by inverting dependencies toward the domain — arrived at independently: Alistair Cockburn's Hexagonal Architecture (2005) frames it as ports and adapters, Jeffrey Palermo's Onion Architecture (2008) frames it as concentric layers around a domain core, and Robert C. Martin's Clean Architecture (2012) synthesizes both into the widely drawn four-ring diagram; in practice they differ mainly in vocabulary and emphasis, not in the underlying constraint.

Hexagonal Architecture's contribution is the explicit primary/secondary port distinction — a primary (driving) port is called by the outside world to invoke your application, a secondary (driven) port is called by your application to reach the outside world — which Clean Architecture absorbs less explicitly into its use-case boundaries. Onion Architecture's contribution is naming the layering itself and being explicit that infrastructure, including the database, is an outer layer like any other, which was a genuinely useful corrective at a time when "the database is the design" was the default mental model. Clean Architecture's main addition is the specific ring names — entities, use cases, interface adapters, frameworks and drivers — and the single Dependency Rule that ties them together. An architect being interviewed should be able to use whichever term the interviewer uses and map it onto the same mental model, rather than insisting on one vocabulary as more correct.

What interviewers look for: recognizing the shared core principle instead of treating the three as unrelated schools of thought, plus knowing at least one distinguishing detail per name (ports and adapters for Hexagonal, the explicit ring diagram for Clean).

Q3 What exactly is a "port" and an "adapter" in Hexagonal Architecture terms, mapped onto a real ASP.NET Core and EF Core application?#

Short answer: A port is an interface your application core defines and depends on; an adapter is a concrete implementation of that port living in an outer layer — a primary port like IPlaceOrderUseCase is called by an ASP.NET Core controller (the primary adapter, translating HTTP into a method call), while a secondary port like IOrderRepository is called by your application and implemented by an EF Core class (the secondary adapter, translating the interface into DbContext calls).

C#
// Port, defined inside the application core.
public interface IOrderRepository
{
    Task<Order?> FindAsync(Guid orderId, CancellationToken ct);
    Task SaveAsync(Order order, CancellationToken ct);
}

// Secondary adapter, in the infrastructure project, implementing the port.
public sealed class EfCoreOrderRepository(AppDbContext db) : IOrderRepository
{
    public Task<Order?> FindAsync(Guid orderId, CancellationToken ct) =>
        db.Orders.FirstOrDefaultAsync(o => o.Id == orderId, ct);

    public Task SaveAsync(Order order, CancellationToken ct) =>
        db.SaveChangesAsync(ct);
}

The controller plays the mirror role on the primary side: it is itself an adapter that translates an HTTP request into a call against a use-case port, and nothing about the use case's interface knows or cares that HTTP is involved — the same port could be called from a message-queue consumer or a console command with no change to the application core.

What interviewers look for: correctly identifying which side is primary versus secondary, since candidates frequently reverse them, and a working code example that shows the port living in the core and the adapter living outside it.

Q4 Where should validation live in a Clean or Onion architecture — the domain, the application layer, or the API boundary?#

Short answer: All three, but for different kinds of validation: input shape and format validation, such as "email is a well-formed string" or "quantity is a positive integer," belongs at the API boundary against the request DTO; orchestration validation that needs external state, such as "does this SKU exist" or "does the customer have enough credit," belongs in the application/use-case layer; and business invariants that must always hold for an entity to be valid, such as "an order line's quantity is never zero," belong inside the domain entity itself, enforced by its constructor and methods so an invalid state is never representable in the first place.

Conflating these leads to two common failures: pushing invariant enforcement out to the application layer, which means every new use case that touches the entity has to remember to re-check the same rule, and the domain type itself can still be constructed in an invalid state by anyone who bypasses that use case; or pushing external-state checks into the domain entity, which forces the entity to depend on a repository or service it should have no knowledge of, violating the Dependency Rule directly. A domain entity should be able to guarantee its own invariants using only the data already inside it — OrderLine's constructor throwing on a zero quantity needs no external dependency — while "does this product still exist" inherently requires reaching outside the entity, which is why it belongs one layer up, in the use case that already has access to the relevant ports.

What interviewers look for: the three-way split by validation kind, not just "validation happens everywhere," and the specific reasoning for why invariants belong in the entity while existence and state checks do not.

Q5 Vertical slice architecture is increasingly recommended over traditional layered Clean Architecture. What problem does it solve, and does it conflict with the Dependency Rule?#

Short answer: Vertical slice architecture organizes code by feature — one folder per use case containing its request, handler and response together — instead of by technical layer, which reduces the ceremony of touching four or five projects to add one endpoint; it does not inherently conflict with the Dependency Rule, because horizontal layering describes the direction dependencies point, while vertical slicing describes how files are organized on disk — the two are orthogonal, and a codebase can slice vertically by feature while each slice still respects inward-pointing dependencies internally.

The problem vertical slices solve is real: a strict multi-project Clean Architecture setup for a single new field on one endpoint can mean touching a request DTO in the API project, a use-case interface in the application project, a use-case implementation, a repository interface, a repository implementation in infrastructure, and a response DTO — six files across four projects for one small change, with most of that ceremony providing no benefit if the feature has no meaningful cross-cutting reuse with anything else. A vertical-slice implementation, often built around a single request/handler pair per feature (sometimes called the REPR pattern — request, endpoint, response), collapses that into one file or one small folder, while still keeping the handler's persistence access behind an interface it doesn't implement itself, if that seam is actually needed. In practice, many production .NET codebases combine the two: vertical slices for organizing individual use cases, with the shared entities, ports and cross-cutting infrastructure still following the Dependency Rule underneath.

What interviewers look for: correctly separating "how dependencies point" from "how files are organized," since many candidates treat vertical slices and Clean Architecture as mutually exclusive when they answer genuinely different questions.

Q6 What's a concrete sign that a team has over-engineered Clean Architecture for the problem they actually have?#

Short answer: The clearest sign is an interface with exactly one implementation, no plan for a second, and a use-case class that does nothing but forward its call to a repository with no additional logic — five or six projects and a dozen interfaces to serve what is functionally a CRUD form, where the layering adds navigation cost and mapping overhead without ever being exercised for its stated purpose of swapping an implementation.

A concrete pattern: IGetCustomerUseCase.ExecuteAsync(id) that does nothing but call _customerRepository.FindAsync(id) and map the result to a response DTO — the use-case layer here adds a file, an interface, a DI registration and a mapping step around a single method call, without any business rule, orchestration, or validation actually happening inside it. Multiply that across twenty similar CRUD operations, each mapped through the same four layers with a DTO conversion at every boundary, and a team ends up maintaining significantly more code than a simpler design would need, for architecture the project's actual complexity never demanded. The tell in code review is a use-case or handler method whose body is a single pass-through call with no conditional logic, no validation and no coordination between more than one port — that is a strong signal the ceremony is not paying for itself in that specific case, even if the same architecture is entirely justified elsewhere in the same codebase.

What interviewers look for: a specific, checkable smell — a pass-through use case with a single caller and callee and no logic of its own — rather than a general "it depends" answer with no concrete detection method.

Q7 How does Clean or Hexagonal architecture affect testability? Walk through the test pyramid you'd build for a use case implemented this way.#

Short answer: The dependency inversion at the core of these architectures is what makes a large, fast unit-test layer possible in the first place: use-case handlers and domain entities depend only on ports, so unit tests substitute fakes or mocks for those ports and never touch a real database, message broker or HTTP call — the test pyramid ends up with many fast unit tests for domain and use-case logic, a smaller layer of integration tests that exercise real adapters against something like Testcontainers-backed databases, and the fewest tests at the top exercising the full stack through the real ASP.NET Core pipeline via WebApplicationFactory.

The unit-test layer is where the architecture pays off most directly: an Order entity's invariant logic and a use case's orchestration logic (call the repository, apply a domain rule, call another port) can be fully tested with in-memory fakes implementing the relevant ports, running in milliseconds with no shared state between tests. The integration layer exists to verify the adapters themselves do what their ports promise — that EfCoreOrderRepository.SaveAsync really persists what it claims to, using a real database engine rather than an in-memory substitute that may not enforce the same constraints. The end-to-end layer, run through WebApplicationFactory against the full composed application, verifies wiring — that the DI container actually resolves correctly and the HTTP pipeline calls the right use case — and stays intentionally small because it is the slowest and most fragile layer.

What interviewers look for: a specific three-layer breakdown mapped onto ports/adapters, with the observation that the volume of tests should be inverted — many fast unit tests, few slow end-to-end ones — because of, not despite, the architecture.

Q8 How do you evolve a Clean Architecture codebase as requirements grow — for example, moving from one relational database adapter to supporting both SQL and a document store, or splitting a module into its own service?#

Short answer: Swapping or adding an adapter behind an already-stable port is the architecture's central promise, and it holds up well when the port was designed around the use case's actual needs rather than around the first implementation's shape; it holds up badly when the port leaked implementation details from day one, such as an IOrderRepository that exposes IQueryable<Order> directly, which quietly requires every future adapter to support LINQ-to-SQL-style translation, making a document-store adapter far harder to write than the pattern implies.

The practical move when adding a second persistence technology is auditing the existing port's method signatures for leaked assumptions before writing the new adapter — a repository interface with explicit, intention-revealing methods like FindActiveOrdersForCustomerAsync(customerId) ports cleanly to a document store, while one built around IQueryable<Order> does not, and may need to be narrowed first. Splitting a module into its own service works best when you first prove the module already has a clean internal boundary, meaning it depends on other modules only through its own ports, with no other module reaching directly into its entities or repositories — extracting that module behind a network boundary is then a matter of turning an in-process port into an HTTP or messaging client, following the same Strangler Fig approach used for any legacy extraction, rather than a redesign of the module's internal architecture.

What interviewers look for: the caveat about leaky ports, since it is the realistic failure mode that separates architects who have actually swapped an adapter in production from those repeating the theoretical promise.

Q9 A junior architect proposes putting EF Core entities directly in the Domain project to save time. What actually goes wrong technically, not just philosophically, if you do this?#

Short answer: Beyond the philosophical Dependency Rule violation, concrete problems follow: navigation properties often need virtual for lazy-loading proxies, which weakens encapsulation because a proxy subclass needs access it shouldn't otherwise have; the Domain project now has a compile-time package reference to Microsoft.EntityFrameworkCore, which drags a data-access dependency into every other project that references Domain, including ones that should never need it; and collection properties frequently end up with public setters or public List<T> types to satisfy the change tracker or migrations tooling, which is exactly the kind of "always valid" invariant enforcement domain entities are supposed to guarantee against.

In practice this also couples migrations to the domain model's compiled shape — a domain-driven rename or restructuring of an entity for purely business reasons now also has to consider migration generation and change-tracking behavior, two concerns that have nothing to do with the business reason for the change. It becomes materially harder to unit-test the entity in isolation, because attributes like [Table] or [ForeignKey], or fluent configuration coupled to the same assembly, imply the entity was designed with a specific database schema in mind rather than with only its own invariants in mind. The fix that keeps EF Core's ergonomics without the coupling is private constructors and backing fields the domain project owns, with all OnModelCreating fluent configuration, including HasField and UsePropertyAccessMode, kept entirely inside the infrastructure project so Domain has zero reference to EF Core at all.

What interviewers look for: specific technical failure modes — lazy-loading proxy requirements, the transitive package dependency, weakened encapsulation for the change tracker's benefit — rather than only "it violates the Dependency Rule" stated abstractly.

Q10 How do you decide whether a project needs Clean or Hexagonal Architecture at all, versus a simpler transaction-script or thin vertical-slice approach?#

Short answer: The decision should turn on domain complexity and expected lifetime, not on the architecture's popularity: a system with real, recurring business rules that need to be enforced consistently across many entry points, a multi-year expected lifetime, and a real chance infrastructure will change underneath it justifies the layering's cost; a short-lived internal tool, a thin CRUD layer over a database with little business logic, or a small team that will own the whole system end to end for its entire life usually does not.

The questions worth asking before recommending it: how much genuine business logic exists versus how much of the system is really data entry and retrieval; how many different entry points, such as an API, a background job and an admin UI, need to enforce the same invariants consistently, since duplicated enforcement across entry points is exactly what a domain layer prevents; whether infrastructure swaps, such as a database migration or a new integration partner, are a realistic near-term possibility or a hypothetical being used to justify the design after the fact; and how much team turnover is expected, since a well-isolated domain layer is easier for a new engineer to reason about without first understanding the entire infrastructure stack. Recommending Clean Architecture because it is the default answer to "how should we architect this," without running through these questions, is itself the over-engineering failure mode the rest of this page describes — the architecture is a tool for managing complexity that actually exists, not a credential.

What interviewers look for: a decision framework with specific questions, rather than a blanket "always use Clean Architecture" or "it depends" with no further detail — architects are expected to have and defend an actual decision process.

Quick-Fire Round#

QuestionAnswer
What does the Dependency Rule actually constrain?Source code dependencies point inward only; outer rings may depend on inner rings, never the reverse.
Who introduced Hexagonal Architecture, and in what year?Alistair Cockburn, in 2005, as ports and adapters.
What is a primary (driving) port?An interface the outside world calls to invoke the application, such as a use-case interface.
Where do business invariants belong?Inside the domain entity itself, enforced so an invalid state can't be constructed.
Do vertical slices conflict with the Dependency Rule?No — slicing is about file organization; the rule is about dependency direction; they're orthogonal.
What's the clearest sign of over-engineered layering?A use case or handler that only forwards a call to a repository with no logic of its own.
What commonly breaks a "swap the adapter" promise in practice?A leaky port, such as one exposing IQueryable<T> directly to its callers.
What EF Core coupling risk comes from putting entities in Domain?A compile-time package reference to Microsoft.EntityFrameworkCore pulled into every dependent project.

How to Prepare#

  • Be able to name the specific contribution of Hexagonal, Onion and Clean Architecture individually, not just treat them as synonyms.
  • Practice the three-way validation split — boundary, orchestration, invariant — with a concrete example for each.
  • Have a clear answer for why vertical slices and layered dependency direction are orthogonal, since interviewers use this to test real understanding versus buzzword recall.
  • Prepare one over-engineering example you can describe precisely, including the specific code smell that revealed it.
  • Rehearse the leaky-port failure mode; it is what separates a candidate who has actually swapped an adapter in production from one repeating the textbook promise.
  • Practice a short decision framework for "does this project need this architecture at all," with specific questions, not a one-line verdict.