skip to content

Application Architecture

How a single application is organised internally around its domain: ports and adapters, the concentric clean and onion layouts, and the UI separation patterns MVC, MVP and MVVM. This is the level most day-to-day design conversations happen at.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

22

In Clean Architecture, the 'Dependency Rule' says source code dependencies can only point inward. If a UseCase class needs to save data to a PostgreSQL database, why shouldn't that UseCase class directly import a JDBC or ORM library?

level: juniorimportance: must knowfreq 75%

answer

  1. arrows point inward only
  2. stable core, volatile edge
  3. interface owned by inner ring
  4. DIP flips the compile dependency
  5. swap tech without touching use cases

basics

~10 s

Inner circles (business rules) must never depend on outer circles (frameworks/DB). The use case defines an interface it needs; a database-specific class outside implements it. This keeps business logic free of database code.

solid answer

~40 s

The Dependency Rule states that source-code dependencies may only point inward, toward higher-level policy. Entities and use cases are the innermost, most stable layer; frameworks, DB drivers, and UI are outermost and most volatile. If a UseCase directly imported JDBC, the business-rule file would depend on a low-level, changeable technology, coupling stable code to unstable code and violating the Dependency Inversion Principle. Instead, the use case declares an interface (e.g. OrderRepository) that it owns; a Gateway/Repository implementation in the infrastructure layer implements it and is wired in at the outer edge, often via a DI container. This lets business logic be compiled, tested, and reasoned about without any database, framework, or UI present at all, and lets you swap Postgres for DynamoDB without touching a single use case.

go deeper

for a junior

Can state that dependencies point inward and give one plausible reason (easier testing, less coupling to the database).

for a middle

Can explain the actual mechanism: the inner ring declares an interface, the outer ring implements it, and a DI container wires the concrete class in at startup.

for a senior

Can discuss how to enforce the rule with tooling (ArchUnit, module boundaries) and can spot violations like framework annotations leaking into use-case classes.

for a principal

Can weigh when the discipline is worth its overhead versus when a simpler, less-inverted structure is the pragmatic choice for the project's actual risk profile.

## The four concentric rings Clean Architecture, popularized by Robert C. Martin ("Uncle Bob") in his 2012 blog post and later the 2017 book Clean Architecture, organizes a system into concentric rings and states one governing rule: the **Dependency Rule**. Source code dependencies - the import or use statements in your code - may only point inward, from outer rings toward inner rings, never the reverse. - **Entities** - the innermost ring holds the most general, most stable business rules and data structures, the kind of logic that would exist even if you changed how the whole application is delivered. - **Use Cases** (sometimes called **Interactors**) - the next ring out holds application-specific business rules that orchestrate entities to accomplish a particular task, like 'place an order' or 'cancel a subscription.' - **Interface Adapters** - outside that sits controllers, presenters, and gateways that translate data between the use-case-friendly format and whatever format the next ring needs. - **Frameworks & Drivers** - the outermost ring is the web framework, the database driver, the UI toolkit, the ORM - the most volatile, most replaceable, most detail-laden code in the system. ## The mechanism: Dependency Inversion The mechanism for keeping dependencies pointing inward when the natural runtime data flow often needs to go the other way (a use case needs to read/write a database, an outer-ring concern) is the **Dependency Inversion Principle**. 1. The inner ring defines an abstraction - an interface - that describes only what it needs, in its own vocabulary, and that interface physically lives inside the inner ring's source tree or package. 2. The outer ring then implements that interface. 3. So in a Kotlin backend, `OrderRepository` (an interface with methods like `findById`, `save`) lives in the use-case package, and `JpaOrderRepository`, which knows about Hibernate, SQL, and connection pools, lives in the infrastructure package and implements `OrderRepository`. The compiled dependency arrow (infrastructure -> use case) now points inward even though the runtime call still flows from use case to infrastructure implementation at execution time. This is the crucial distinction the rule turns on: it governs **source-level dependency direction**, not the direction of a runtime function call. ## Why does this exist? The core problem it solves is protecting the parts of the system that encode genuine, hard-won business knowledge - what 'placing an order' actually means for this company - from churn caused by parts of the system that are essentially interchangeable technology choices. Databases get migrated, web frameworks get upgraded or replaced, UI toolkits go in and out of fashion, but the rule 'an order cannot ship without a valid payment' rarely changes for reasons related to any of that. By making the use case ignorant of JPA, Spring, or the specific SQL dialect in use: - you can unit-test the use case with a fake in-memory implementation of `OrderRepository` in milliseconds, with zero database, zero HTTP server, and zero framework bootstrap; - you can also swap Postgres for DynamoDB, or REST for gRPC, by writing new adapters, without touching a single use-case class - a genuine, measurable reduction in blast radius for infrastructure changes. ## The trade-off The trade-off is that this indirection is not free. Every one of these boundaries typically means: - an extra **interface**; - at least one extra **implementation class**; - often a **DTO or mapping layer** to convert between the use case's domain model and whatever shape the database, JSON API, or third-party SDK expects. For a small CRUD service with two entities and a handful of endpoints, this ceremony can genuinely cost more in code volume and onboarding time than it saves, and teams frequently over-apply the pattern to systems that will never actually swap a database or a UI, paying the tax without ever collecting the benefit. ## Failure modes 1. The failure mode that shows up most often in production codebases is what's sometimes called an **anemic or 'pass-through' use case**: an interactor class that does nothing but immediately delegate to a repository call, adding a layer of indirection with zero actual business logic in it - a sign the abstraction is being applied mechanically rather than where it earns its keep. 2. A second common failure is **leakage in the other direction**: JPA `@Entity` annotations, Spring `@Component` annotations, or Jackson `@JsonProperty` annotations end up directly on domain/use-case classes 'because it's convenient,' which silently violates the Dependency Rule even though the code still compiles - the use-case package now has a hard source dependency on `javax.persistence` or `com.fasterxml.jackson`, an outer-ring framework, and can no longer be unit tested or reused without dragging that framework along. ## A worked example A concrete example: a payment-processing use case class `ProcessRefundInteractor` depends only on a `RefundGateway` interface it declares itself. - In production, `StripeRefundGateway` implements it by calling the Stripe SDK. - In tests, `FakeRefundGateway` implements it with an in-memory list. Both satisfy the same interface, so the interactor's logic - validating the refund amount against the original charge, checking refund eligibility windows - is tested without a network call, and the team was later able to add a second payment provider adapter without editing the interactor at all.

  • How would you enforce the Dependency Rule automatically in CI, rather than relying on code review to catch violations?
    Use an architecture-testing tool (e.g. ArchUnit for JVM, dependency-cruiser for JS/TS) that scans import statements and fails the build if a package in the use-case or entity layer imports anything from an infrastructure or framework package. This turns a convention into a hard, automated constraint that can't silently regress.
  • At runtime, the use case still calls the repository implementation to fetch data. Doesn't that mean the dependency really does point outward?
    The runtime call and the source dependency are different things. The use case calls a method on an interface it owns; the concrete implementation is looked up (usually via a DI container) and injected at startup. The compiled 'import'/'implements' relationship still points from the outer implementation class inward to the interface, satisfying the rule even though control flows outward at runtime.
  • Can Entities depend on Use Cases in this model?
    No - Entities are the innermost, most general ring and Use Cases sit one ring further out, so dependencies must point from Use Cases toward Entities, never the reverse. An entity that imported a use-case class would be depending on more volatile, application-specific logic, inverting the intended stability gradient.

Like a hospital's medical protocol for treating a heart attack: the protocol just says 'record the patient outcome' - it doesn't care whether the hospital's records system is Epic or Cerner. Whichever system is plugged in has to conform to the protocol's needs, not the other way around.

saying these in an interview costs you the question

  • suggests importing a database/ORM class directly into a use case 'for convenience'
  • describes the Dependency Rule as being about runtime call order rather than compile-time import direction
  • can't name what mechanism (DIP/interface ownership) lets the runtime call still satisfy an inward-pointing dependency
  • thinks Entities can depend on Use Cases
  • claims the rule means outer-ring code (frameworks, DB) can never be used at all

context

open as a page

In Hexagonal Architecture (Ports & Adapters), what is a 'port', and what distinguishes a driving (primary) port from a driven (secondary) port?

level: juniorimportance: must knowfreq 75%

basics

~20 s

A port is an interface the core app defines. Driving ports are ways the outside world calls INTO the app (like an API). Driven ports are ways the app calls OUT to things it needs (like a database).

open as a page

In the classic MVC (Model-View-Controller) pattern, what job does each of the Model, View, and Controller play, and why does splitting responsibilities this way help maintainability?

level: juniorimportance: must knowfreq 85%

basics

~10 s

Model holds data and business rules, View shows it on screen, Controller takes user input and decides what happens next. Splitting them means you can change how something looks without touching how it works.

open as a page

In Onion Architecture, why is the domain model placed at the center of the design instead of the database or UI?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Onion Architecture puts the business rules in the middle of the app, like the core of an onion, and everything else (database, web framework, UI) sits in layers wrapped around it. The middle never needs to know about the outer layers.

open as a page

Clean Architecture draws four concentric rings - Entities, Use Cases, Interface Adapters, and Frameworks & Drivers. For a typical web application, what kind of code lives in each of these four rings, and how does data get from an outer ring's format into a form the innermost ring can use?

level: middleimportance: must knowfreq 80%

basics

~20 s

Entities hold core business rules; Use Cases run application-specific workflows; Interface Adapters translate between the app and the outside world (controllers, presenters); Frameworks/Drivers are the actual web server, DB, UI. Data crosses rings as plain data structures, converted at each boundary.

open as a page

In Hexagonal Architecture, why does the application core never hold a compile-time dependency on any adapter, and what mechanism makes that possible given that at runtime the core's logic still has to run inside a real web server or database transaction?

level: middleimportance: must knowfreq 70%

basics

~20 s

The core only knows about interfaces it defines itself, never concrete tech classes. A separate wiring step (like a dependency-injection setup) plugs the real adapters in at startup, so the core's code never imports database or web code directly.

open as a page

In MVP (Model-View-Presenter), how does the Presenter's relationship with the View differ from the Controller's relationship with the View in classic MVC, and what problem does that difference solve?

level: middleimportance: must knowfreq 70%

basics

~20 s

In MVP, the View is a dumb interface and the Presenter tells it exactly what to display, instead of the View pulling data from the Model itself like in MVC. This makes the app's logic testable without a real screen.

open as a page

In Onion Architecture, how exactly does the Dependency Inversion Principle let the domain and application rings avoid depending on the database, and what would a violation of that rule look like in real code?

level: middleimportance: must knowfreq 60%

basics

~20 s

The inner code defines an interface saying what it needs (like 'save this order'); the outer database code writes a class that fulfills that interface. The inner code never imports database code directly — it just calls the interface, and something else plugs in the real database class at startup.

open as a page

In Onion Architecture, what distinguishes a domain service from an application service, and which ring does each belong to?

level: middleimportance: must knowfreq 50%

basics

~20 s

A domain service holds business rules that don't fit inside one entity, like 'does this transfer amount exceed the daily limit.' An application service coordinates a whole use case step by step, like 'validate, call the domain service, save, notify.' Domain services sit closer to the center; application services wrap around them.

open as a page

Clean Architecture adds interfaces, DTOs, and mapping code at every ring boundary. What concrete trade-offs does that indirection buy you, and in what kind of project would a senior engineer recommend against applying the full four-ring structure?

level: seniorimportance: must knowfreq 60%

basics

~20 s

You gain testability and the ability to swap frameworks/DBs later, but you pay in more files, more boilerplate, and slower ramp-up. For a small short-lived CRUD app that will never swap its DB or framework, skip the full structure.

open as a page

You're designing a 'TransferFunds' use case between two bank accounts using Hexagonal Architecture. Walk through what the driving port, the application core's responsibilities, and the driven ports would be, and explain why the application core is described as the 'dependency anchor' of the whole design.

level: seniorimportance: must knowfreq 60%

basics

~20 s

The core holds the transfer rules (enough balance? valid accounts?). A driving port lets something like an API call 'do this transfer.' Driven ports let the core ask for things it needs, like reading and saving account balances, without knowing how those things are actually done.

open as a page

In MVVM (Model-View-ViewModel), what mechanism lets the View stay in sync with the ViewModel's state without the ViewModel ever referencing the View directly, and how does that differ from how a Presenter updates a View in MVP?

level: seniorimportance: must knowfreq 80%

basics

~20 s

The ViewModel exposes its state as observable values, like a stream or property that broadcasts changes, and the View watches those values and updates itself automatically. The ViewModel never has to know the View exists or call it directly.

open as a page

Robert C. Martin's phrase 'screaming architecture' argues that a codebase's top-level structure should scream something specific about the system. What should it scream, and what's an example of an architecture that screams the wrong thing?

level: middleimportance: should knowfreq 45%

basics

~10 s

The top-level folders should announce what the app DOES (its use cases/domain), like 'Shipping' or 'PatientIntake' - not what framework it's built with, like 'controllers/models/views.'

open as a page

How does Hexagonal Architecture make it easier to write fast, reliable unit tests for business logic compared to a typical layered (controller to service to repository) architecture where the service layer directly calls concrete repository and client classes?

level: middleimportance: should knowfreq 65%

basics

~20 s

Because business logic only talks to interfaces, tests can swap in fake, in-memory versions of the database or payment gateway instead of the real ones, so tests run fast with no network or database needed, while still checking real business rules.

open as a page

Clean Architecture's Interface Adapters ring contains Controllers and Presenters that sit between Use Cases and the outside world. Concretely, what data crosses a use-case boundary in each direction, and why shouldn't a use case just accept and return its own Entity objects directly to a REST controller?

level: seniorimportance: should knowfreq 55%

basics

~20 s

A controller converts an incoming request into a simple input object the use case understands; the use case returns a simple output object; a presenter turns that into a view model/JSON. Passing entities straight out would leak internal business objects (and their behavior) into the web layer.

open as a page

What are the concrete costs of adopting Hexagonal Architecture for a service, and what characteristics of a project should make a team hesitate before applying it?

level: seniorimportance: should knowfreq 55%

basics

~20 s

It means more files and interfaces for simple stuff, and it only pays off if you actually need to swap technology or test business logic heavily. For a tiny app with one database and one UI that will never change, it's often just extra work with no real benefit.

open as a page

Given a new mobile app screen with a handful of simple, mostly-static fields versus a complex form with many interdependent, frequently changing fields, how would you decide between MVP and MVVM for each, and what's the actual cost you're trading off?

level: seniorimportance: should knowfreq 60%

basics

~20 s

For simple screens, MVP's explicit code is easy to follow and not much extra work. For complex screens with lots of changing state, MVVM's automatic updates save a ton of repetitive code, but you trade that for a debugging chain that's harder to trace step by step.

open as a page

What are the concrete costs of adopting Onion Architecture for a project, and what kind of project would justify deliberately not using it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

It adds extra files and steps (interfaces, mapping code) even for simple features, which slows down small or short-lived projects. If the app is a small CRUD tool or throwaway prototype with a stable, simple database, that extra structure often isn't worth the time it costs.

open as a page

Across MVC, MVP, and MVVM, the Controller/Presenter/ViewModel all sit between View and Model, but none of them is supposed to hold real business logic. In practice, why does business logic keep leaking into that middle layer, and how would you architecturally prevent it at scale?

level: principalimportance: should knowfreq 55%

basics

~20 s

Business rules keep sneaking into the Controller/Presenter/ViewModel because that's the easiest place to add 'just one more check' while wiring a screen. The fix is a separate, UI-independent layer, like domain services, that owns the rules, with the middle layer just calling it.

open as a page

Onion Architecture, Clean Architecture, and Hexagonal Architecture are often used interchangeably in job descriptions and blog posts. Structurally, how does Onion Architecture relate to the other two, and what does it actually add or change relative to a plain layered architecture?

level: principalimportance: should knowfreq 40%

basics

~20 s

All three put business rules in the middle and infrastructure on the outside, with dependencies pointing inward — they're basically siblings with different names for similar ideas. Onion Architecture is the one that talks about rings (domain, domain services, application services, infrastructure); the others use different vocabulary for similar ideas.

open as a page

Clean Architecture, Hexagonal Architecture, and Onion Architecture are frequently cited together as 'the same idea with different diagrams.' At the level of what each one actually prescribes, what is Clean Architecture's specific structure, and what's genuinely different about it versus just being a relabeling of the other two?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

All three push business logic to the center and keep dependencies pointing inward. Clean Architecture specifically names four rings (Entities, Use Cases, Interface Adapters, Frameworks/Drivers) and pairs that with the Dependency Rule and 'screaming architecture' - a bit more prescriptive about layer count and naming than the other two.

open as a page

What are some common anti-patterns that appear when Hexagonal Architecture is applied across a larger codebase or multiple teams, beyond simple leaky ports, and how do they undermine the pattern's intent?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

At scale, teams often make too many tiny interfaces (a mess to navigate), write fakes that don't match real behavior, or build one giant port instead of several focused ones — each quietly brings back the same coupling and confusion the pattern was meant to prevent.

open as a page