Software Architecture & Design Principles

Clean Architecture Boundaries

The Dependency Rule: source-code dependencies may only point inward, from mechanism toward policy. Concentric layers — Entities, Use Cases, Interface Adapters, Frameworks & Drivers — keep business rules ignorant of the database, the UI, and the framework du jour.
  • Dependency Rule: nothing in an inner circle may know anything about an outer circle
  • Entities = enterprise-wide business rules; Use Cases = application-specific business rules that orchestrate entities
  • Interface Adapters translate between use-case-friendly data and framework/DB/UI-friendly data
  • Frameworks & Drivers (web framework, database, UI toolkit) are the outermost, most volatile, most replaceable ring
  • Crossing a boundary inward-to-outward uses an interface owned by the inner ring; the outer ring implements it (dependency inversion in practice)
  • Data crossing boundaries should be simple structures (DTOs), never framework/ORM entities leaking into business rules
The four rings, outside in
RingContainsKnows about
Frameworks & DriversWeb framework, DB driver, UI, external servicesEverything inward
Interface AdaptersControllers, Presenters, GatewaysUse Cases' interfaces; converts to/from framework shapes
Use CasesApplication-specific business rules (interactors)Entities only
EntitiesEnterprise-wide business rules and dataNothing outward — pure domain logic

The Dependency Rule is the one non-negotiable constraint: "source code dependencies can only point inwards." A UserRepository interface lives with the use case that needs it (inner ring); a SqlUserRepository implementation lives in the outer ring and implements that interface. The use case never imports anything from the persistence framework — it depends on an abstraction it owns, and the database code depends on that abstraction too, just from the other side. This is Dependency Inversion And Injection applied at the architectural seam, and it is what makes the business rules testable without a real database (Testing Philosophy).

Crossing the boundary through an owned interface
// Inner ring (use case) defines what it needs, in its own vocabulary:
public interface UserRepository {
    Optional<User> findById(String id);
}

public class RegisterUserInteractor {
    private final UserRepository users;   // depends on the abstraction, not on SQL
    RegisterUserInteractor(UserRepository users) { this.users = users; }
    // ... business rule uses `users` without knowing it is Postgres, Mongo, or memory
}

// Outer ring (frameworks & drivers) implements it:
public class PostgresUserRepository implements UserRepository {
    public Optional<User> findById(String id) { /* SQL here */ }
}
Sources
  • Clean ArchitectureCh. 22 — The Clean Architecture