La Regla de Dependencia en Clean Architecture
Las dependencias del código fuente solo pueden apuntar hacia adentro — nada en un círculo interior puede saber nada de un círculo exterior, manteniendo el núcleo de negocio estable y reutilizable.
Por qué importa
Clean Architecture está organizada como cuatro círculos concéntricos: Entidades (más interno) → Casos de Uso → Adaptadores de Interfaz → Frameworks y Drivers (más externo). La Regla de Dependencia es la única restricción que hace que esto funcione: las dependencias del código fuente solo pueden apuntar hacia adentro.
Nada en un círculo interior puede conocer nada de algo en un círculo exterior. El nombre de una función, una clase, una variable, o cualquier entidad de software declarada en un círculo exterior no debe mencionarse en el código de un círculo interior. Esto incluye los formatos de datos usados en un círculo exterior — los datos que cruzan una frontera deben convertirse a la forma DTO simple que el círculo interior conoce.
Esta regla es lo que hace que el núcleo sea reutilizable. Una Entidad que importa SQLAlchemy no es reutilizable sin SQLAlchemy. Un Caso de Uso que importa Flask no es testeable sin Flask. Los círculos interiores son estables precisamente porque nada en ellos llega hacia afuera. Cada importación en tu capa de dominio es una violación potencial.
✗El problema
An Order entity (innermost circle) that imports SQLAlchemy, Flask, and Stripe simultaneously — the innermost layer depends on all three outermost layers.
Bad
from sqlalchemy import Column, Float, Integer
from sqlalchemy.ext.declarative import declarative_base
from flask import request
import stripe
Base = declarative_base()
class Order(Base): # depends on SQLAlchemy (outermost)
__tablename__ = "orders"
id = Column(Integer, primary_key=True)
total = Column(Float)
def charge(self):
token = request.json["token"] # depends on Flask (outermost)
stripe.Charge.create( # depends on Stripe (outermost)
amount=int(self.total * 100),
currency="usd",
source=token,
)
# The innermost circle depends on three outermost circles.
# Cannot test charge() without Flask running and Stripe credentials.
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"; // Frameworks
import { Request } from "express"; // Frameworks
import Stripe from "stripe"; // Frameworks
@Entity()
export class Order {
@PrimaryGeneratedColumn() id!: number;
@Column("float") total!: number;
async charge(req: Request): Promise {
const stripe = new Stripe(process.env.STRIPE_KEY!, { apiVersion: "2023-10-16" });
const token = (req.body as any).token;
await stripe.charges.create({ amount: Math.round(this.total * 100), currency: "usd", source: token });
}
}
// Entity (innermost) imports Framework layer (outermost) — Dependency Rule violated.
✓La solución
A pure Order entity with only domain state and rules. Outer layers (StripeGateway, PlaceOrderUseCase) know about Order — Order knows nothing of them.
Good
from dataclasses import dataclass
from decimal import Decimal
@dataclass
class Order:
order_id: str
items: list
status: str = "pending"
def calculate_total(self) -> Decimal:
"""Critical Business Rule — exists regardless of payment provider."""
return sum(item.subtotal() for item in self.items)
def mark_paid(self) -> None:
"""Domain rule — an order can only be paid once."""
if self.status != "pending":
raise ValueError(f"Cannot pay order with status: {self.status}")
self.status = "paid"
# Outer layers depend on Order; Order depends on nothing outer.
# PaymentService (Use Cases) calls order.mark_paid() after Stripe succeeds.
# StripeGateway (Interface Adapters) knows about Stripe; Order does not.
# Test: order = Order("1", items); order.mark_paid() — no mocks needed.
export class Order {
private _status: "pending" | "paid" | "cancelled" = "pending";
constructor(readonly orderId: string, readonly items: readonly OrderItem[]) {}
get status() { return this._status; }
calculateTotal(): number {
return this.items.reduce((sum, item) => sum + item.subtotal(), 0);
}
markPaid(): void {
// Domain rule — independent of any payment provider
if (this._status !== "pending") {
throw new Error(\`Cannot pay an order with status: \${this._status}\`);
}
this._status = "paid";
}
}
// StripePaymentGateway (Interface Adapters) knows about Stripe; Order does not.
// PlaceOrderUseCase (Use Cases) calls order.markPaid() after payment succeeds.
// Test: const order = new Order("1", items); order.markPaid(); — zero mocks.
💡Conclusión clave
La Regla de Dependencia es la restricción más importante en Clean Architecture. Las dependencias del código fuente deben apuntar solo hacia adentro — los círculos internos no conocen nada de los círculos externos. Cada importación en tu capa de dominio es una violación potencial. Las Entidades son estables porque no dependen de nada que pueda cambiar debajo de ellas.
🔧 Algunos ejercicios pueden tener errores. Si algo parece incorrecto, usa el botón Feedback (abajo a la derecha) para reportarlo — nos ayuda a corregirlo rápido.
Pista: La Regla de Dependencia es la restricción más importante en Clean Architecture. Cada import en tu capa de dominio es una violación potencial.
✗ Tu versión