Skip to main content

Clean

Clean Architecture, proposed by Robert C. Martin (Uncle Bob), organizes a system into concentric layers with the most important business rules at the center and less important technical details on the outside.

The core idea:

Dependencies must always point inward toward the business rules.

This ensures:

  • Business logic is independent of UI, databases, frameworks, and external services.
  • External technologies can be replaced without affecting the core.

The Four Main Layers​

From inside β†’ outside:

Entities (Enterprise Business Rules)​

  • Represent core business objects and rules.
  • Are independent of application logic and technology.

Examples: User, Order, Invoice, Account.

Use Cases (Application Business Rules)​

  • Orchestrate how entities are used.
  • Contain application-specific business rules.
  • Define what the system can do.

Examples: PlaceOrder, TransferMoney, RegisterUser.

Interface Adapters​

  • Convert data between formats required by:
    • UI β†’ Use Cases
    • Use Cases β†’ Database/External services
  • Includes controllers, presenters, gateways, repositories.

Frameworks & Drivers​

  • Outermost layer.
  • Contains UI frameworks, databases, web servers, messaging systems, and external APIs.
  • These are replaceable details.

Dependency Rule​

All source code dependencies must point inward.

That means:

  • Use cases depend on entities.
  • Interface adapters depend on use cases.
  • Frameworks depend on interface adapters.
  • No inward layer depends on an outward layer.

Example: Online Ordering System​

Let’s design a Place Order feature using Clean Architecture.

1. Entities Layer​

public class Order {
private int customerId;
private List<Item> items;

public Order(int customerId, List<Item> items) {
this.customerId = customerId;
this.items = items;
}

public double calculateTotal() {
return items.stream().mapToDouble(Item::getPrice).sum();
}
}

Pure business object, no frameworks, no DB, no UI.

2. Use Cases Layer​

Input Boundary (Use Case Interface)​

public interface PlaceOrderInputBoundary {
void execute(PlaceOrderRequestModel request);
}

Request Model​

public class PlaceOrderRequestModel {
public int customerId;
public List<Item> items;
}

Use Case Interactor​

public class PlaceOrderInteractor implements PlaceOrderInputBoundary {
private final OrderGateway orderGateway;
private final PaymentGateway paymentGateway;

public PlaceOrderInteractor(OrderGateway orderGateway, PaymentGateway paymentGateway) {
this.orderGateway = orderGateway;
this.paymentGateway = paymentGateway;
}

@Override
public void execute(PlaceOrderRequestModel request) {
Order order = new Order(request.customerId, request.items);
paymentGateway.processPayment(order);
orderGateway.save(order);
}
}

Coordinates entities and business rules, but knows nothing about frameworks or databases.

3. Interface Adapters Layer​

Output Boundary (Presenter Interface)​

public interface PlaceOrderOutputBoundary {
void present(PlaceOrderResponseModel response);
}

Response Model​

public class PlaceOrderResponseModel {
public String status;
}

Controller​

public class PlaceOrderController {
private final PlaceOrderInputBoundary inputBoundary;

public PlaceOrderController(PlaceOrderInputBoundary inputBoundary) {
this.inputBoundary = inputBoundary;
}

public void handleRequest(int customerId, List<Item> items) {
PlaceOrderRequestModel request = new PlaceOrderRequestModel();
request.customerId = customerId;
request.items = items;
inputBoundary.execute(request);
}
}

Converts UI input into a format the use case understands.

Gateways (Interfaces)​

public interface OrderGateway {
void save(Order order);
}

public interface PaymentGateway {
void processPayment(Order order);
}

These are defined inward, but implemented outward.

4. Frameworks & Drivers Layer​

Database Implementation​

public class JpaOrderGateway implements OrderGateway {
@Override
public void save(Order order) {
// Save using JPA / SQL
}
}

Payment API Adapter​

public class StripePaymentGateway implements PaymentGateway {
@Override
public void processPayment(Order order) {
// Call Stripe API
}
}

Web UI Controller (Framework)​

@RestController
public class OrderRestController {
private final PlaceOrderController controller;

@PostMapping("/orders")
public void placeOrder(@RequestBody OrderRequestDTO dto) {
controller.handleRequest(dto.getCustomerId(), dto.getItems());
}
}

These are replaceable technical details.

How the Flow Works​

  1. User sends HTTP request β†’ Web controller.
  2. Web controller calls application controller.
  3. Controller creates request model β†’ passes to use case.
  4. Use case creates entity β†’ processes business rules.
  5. Use case calls gateway interfaces.
  6. Gateways are implemented by infrastructure adapters.
  7. Response flows back outward.

Advantages of Clean Architecture

  • Business logic independent of frameworks
  • Highly testable (mock boundaries)
  • Easy to change UI, database, or external services
  • Encourages SOLID principles
  • Long-term maintainability

Disadvantages

  • More boilerplate code
  • Steeper learning curve
  • Overkill for very small projects

Clean Architecture vs Hexagonal vs Layered​

FeatureLayeredHexagonalClean Architecture
StructureHorizontal layersCore + ports/adaptersConcentric rings
Dependency DirectionTop β†’ DownInwardInward
Core independent of frameworks❌ Sometimesβœ… Yesβœ… Yes
EmphasisTechnical separationBoundary isolationBusiness rules first