Skip to main content

Inicia sesión en CleanKata

Sigue tu progreso, gana XP y desbloquea todas las lecciones.

Al iniciar sesión aceptas nuestros Términos de uso y Política de privacidad.

Arquitectura Limpia80 XP8 min

Anatomía de un Límite: Cruzando Límites

Al cruzar un límite arquitectónico, las dependencias del código fuente deben apuntar en sentido contrario al flujo de control — el polimorfismo hace esto posible independientemente de quién llama a quién.

Por qué importa

Cuando un componente de alto nivel llama a uno de bajo nivel, tanto el flujo de control como la dependencia del código fuente apuntan naturalmente en la misma dirección — hacia abajo. Este es el comportamiento por defecto, y es arquitectónicamente incorrecto. Significa que cambiar el componente de bajo nivel fuerza una recompilación del de alto nivel. Significa que no puedes probar el componente de alto nivel sin que el de bajo nivel esté presente.

La solución es invertir la dependencia usando una interfaz. El componente de alto nivel define una interfaz que describe lo que necesita. El componente de bajo nivel implementa esa interfaz. En tiempo de ejecución, el control sigue fluyendo de alto a bajo. Pero en el código fuente, el módulo de bajo nivel ahora depende de la interfaz del de alto nivel — apuntando hacia arriba. Esta es la Inversión de Dependencias a nivel de frontera arquitectónica. Así funcionan las fronteras en Clean Architecture, puertos y adaptadores, y arquitectura hexagonal por igual.

✗El problema

PaymentProcessor (high-level) directly imports StripeClient (low-level) — control flow and source dependency both point downward. No inversion. Tightly coupled to Stripe.

Bad

# stripe_client.py (low-level detail)
import stripe

class StripeClient:
    def charge(self, card_token: str, amount: float) -> dict:
        return stripe.PaymentIntent.create(
            amount=int(amount * 100), currency="usd",
            payment_method=card_token, confirm=True)

# payment_processor.py (high-level policy)
from stripe_client import StripeClient  # HIGH-LEVEL imports LOW-LEVEL — wrong!

class PaymentProcessor:
    def __init__(self):
        self._stripe = StripeClient()

    def process(self, order_id: str, card_token: str, amount: float) -> bool:
        result = self._stripe.charge(card_token, amount)
        return result["status"] == "succeeded"

# Flow of control:   PaymentProcessor → StripeClient (downward)
# Source dependency: PaymentProcessor → StripeClient (same direction — no inversion)
# To test: need a real Stripe account or complex patching.
// stripe-client.ts (low-level)
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_KEY!);
export class StripeClient {
  async charge(cardToken: string, amount: number): Promise {
    const intent = await stripe.paymentIntents.create({
      amount: Math.round(amount * 100), currency: "usd",
      payment_method: cardToken, confirm: true,
    });
    return intent.status === "succeeded";
  }
}

// payment-processor.ts (high-level)
import { StripeClient } from "./stripe-client"; // imports the detail — wrong!

export class PaymentProcessor {
  private stripe = new StripeClient();
  async process(orderId: string, cardToken: string, amount: number): Promise {
    return this.stripe.charge(cardToken, amount);
  }
}
// Control and dependency both flow downward. No boundary. No inversion.

✓La solución

PaymentGateway interface lives in the high-level module. StripeGateway (low-level) depends on it. Control flows down at runtime; dependency points up in source code.

Good

from abc import ABC, abstractmethod

# payment_gateway.py — interface defined in the HIGH-level module
class PaymentGateway(ABC):
    @abstractmethod
    def charge(self, card_token: str, amount: float) -> bool: ...

# payment_processor.py (high-level) — depends on its own abstraction
class PaymentProcessor:
    def __init__(self, gateway: PaymentGateway):
        self._gateway = gateway

    def process(self, order_id: str, card_token: str, amount: float) -> bool:
        return self._gateway.charge(card_token, amount)

# stripe_gateway.py (low-level) — depends on the HIGH-level interface
import stripe
from payment_gateway import PaymentGateway  # LOW-LEVEL imports HIGH-LEVEL!

class StripeGateway(PaymentGateway):
    def charge(self, card_token: str, amount: float) -> bool:
        result = stripe.PaymentIntent.create(
            amount=int(amount * 100), currency="usd",
            payment_method=card_token, confirm=True)
        return result["status"] == "succeeded"

# Flow of control:   PaymentProcessor → StripeGateway (runtime, downward)
# Source dependency: StripeGateway → PaymentGateway ← PaymentProcessor (inverted!)
# Test PaymentProcessor with a stub — no Stripe needed.
// domain/payment-gateway.ts — interface lives with the HIGH-level policy
export interface PaymentGateway {
  charge(cardToken: string, amount: number): Promise;
}

// domain/PaymentProcessor.ts (high-level) — imports only its own interface
import type { PaymentGateway } from "./payment-gateway";

export class PaymentProcessor {
  constructor(private gateway: PaymentGateway) {}

  async process(orderId: string, cardToken: string, amount: number): Promise {
    return this.gateway.charge(cardToken, amount);
  }
}

// infra/StripeGateway.ts (low-level) — implements the HIGH-level interface
import Stripe from "stripe";
import type { PaymentGateway } from "../domain/payment-gateway"; // points UPWARD

export class StripeGateway implements PaymentGateway {
  private client = new Stripe(process.env.STRIPE_KEY!);
  async charge(cardToken: string, amount: number): Promise {
    const intent = await this.client.paymentIntents.create({
      amount: Math.round(amount * 100), currency: "usd",
      payment_method: cardToken, confirm: true,
    });
    return intent.status === "succeeded";
  }
}
// Source dependencies: StripeGateway → PaymentGateway ← PaymentProcessor
// Runtime control:     PaymentProcessor → StripeGateway
// The interface makes the dependency flow opposite to the control flow.

💡Conclusión clave

La interfaz pertenece al componente de ALTO nivel, no al de bajo nivel. El componente de bajo nivel depende de la interfaz del de alto nivel — no al revés. Esto es lo que hace que cruzar una frontera sea seguro: el control en tiempo de ejecución puede fluir en cualquier dirección mientras las dependencias de código fuente siempre apuntan hacia la política estable.

🔧 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 interfaz pertenece al componente de ALTO nivel, no al de bajo nivel. El componente de bajo nivel depende de la interfaz del alto nivel, no al revés.

✗ Tu versión

Anatomía de un Límite: Cruzando Límites — CleanKata — CleanKata