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 Limpia70 XP7 min

Modos de Desacoplamiento: Fuente, Despliegue y Servicio

El desacoplamiento puede ocurrir a nivel de código fuente, despliegue o servicio — una buena arquitectura te permite empezar como monolito y evolucionar hacia servicios si los límites están bien definidos.

Por qué importa

Existen tres modos de desacoplamiento, cada uno con diferentes costos y beneficios. El desacoplamiento a nivel de código fuente significa código fuente compartido en un único desplegable — los equipos pueden trabajar en módulos separados pero aún chocan en el momento del despliegue. El desacoplamiento a nivel de despliegue significa artefactos compilados separados (paquetes, JARs, DLLs) que pueden desplegarse independientemente — más rápido publicar un componente sin recompilar los demás. El desacoplamiento a nivel de servicio significa procesos separados comunicándose a través de una red — el mayor aislamiento, pero también el mayor costo (latencia, fallos de red, serialización de datos).

La clave es no saltar directamente al desacoplamiento a nivel de servicio. Un monolito con fronteras bien trazadas — usando puertos y adaptadores abstractos — está arquitectónicamente listo para dividirse en servicios cuando la necesidad de negocio lo requiera. Un sistema distribuido mal estructurado es el peor resultado: pagas el costo de red pero no obtienes ninguno de los beneficios de aislamiento.

✗El problema

Orders and inventory tightly coupled via direct imports and a shared database session — impossible to split without major surgery, impossible to scale independently.

Bad

# orders/service.py
from inventory.manager import InventoryManager  # direct coupling

class OrderService:
    def __init__(self, db):
        self.db        = db
        self.inventory = InventoryManager(db)   # shared DB session!

    def place(self, user_id, item_id, qty):
        if not self.inventory.check_and_decrement(item_id, qty):
            raise ValueError("Out of stock")
        return self.db.insert("orders", {"user_id": user_id, "item_id": item_id})

# inventory/manager.py
class InventoryManager:
    def __init__(self, db): self.db = db
    def check_and_decrement(self, item_id, qty):
        item = self.db.find("inventory", item_id)
        if item["stock"] < qty: return False
        self.db.update("inventory", item_id, {"stock": item["stock"] - qty})
        return True

# Shared DB session: inventory can never move to its own database.
# Direct import: extracting to a microservice requires rewriting both classes.
// orders/OrderService.ts
import { InventoryManager } from "../inventory/InventoryManager"; // direct coupling
import { SharedDatabase } from "../shared/Database";

export class OrderService {
  private inventory: InventoryManager;
  constructor(private db: SharedDatabase) {
    this.inventory = new InventoryManager(db);  // hard-wired, shared DB
  }

  async place(userId: string, itemId: string, qty: number): Promise {
    const ok = await this.inventory.checkAndDecrement(itemId, qty);
    if (!ok) throw new Error("Out of stock");
    return this.db.insert("orders", { userId, itemId, qty });
  }
}
// To split into microservices: rewrite both classes, handle distributed transactions.
// The boundary was never drawn — now there's nothing to promote to a service.

✓La solución

An abstract port defines the boundary — today a function call (source-level), tomorrow a REST call (service-level). OrderService never changes.

Good

from abc import ABC, abstractmethod

class InventoryPort(ABC):       # defined on the orders side
    @abstractmethod
    def reserve(self, item_id: str, qty: int) -> bool: ...

class OrderService:
    def __init__(self, order_repo, inventory: InventoryPort):
        self._orders    = order_repo
        self._inventory = inventory

    def place(self, user_id: str, item_id: str, qty: int) -> str:
        if not self._inventory.reserve(item_id, qty):
            raise ValueError("Out of stock")
        return self._orders.create(user_id, item_id, qty)

# Today — in-process adapter (source-level decoupling)
class InProcessInventoryAdapter(InventoryPort):
    def __init__(self, svc): self._svc = svc
    def reserve(self, item_id, qty): return self._svc.reserve(item_id, qty)

# Tomorrow — HTTP adapter (service-level decoupling, OrderService unchanged)
class HttpInventoryAdapter(InventoryPort):
    def __init__(self, url): self._url = url
    def reserve(self, item_id, qty):
        import httpx
        r = httpx.post(f"{self._url}/reserve", json={"item_id": item_id, "qty": qty})
        return r.json()["reserved"]
// orders/ports/InventoryPort.ts — interface owned by the orders domain
export interface InventoryPort {
  reserve(itemId: string, qty: number): Promise;
}

// orders/OrderService.ts — depends only on the port
export class OrderService {
  constructor(
    private orders: OrderRepository,
    private inventory: InventoryPort,
  ) {}

  async place(userId: string, itemId: string, qty: number): Promise {
    const ok = await this.inventory.reserve(itemId, qty);
    if (!ok) throw new Error("Out of stock");
    return this.orders.create(userId, itemId, qty);
  }
}

// Today — source-level decoupling (monolith)
export class InProcessInventoryAdapter implements InventoryPort {
  constructor(private svc: InventoryService) {}
  reserve(itemId: string, qty: number) { return this.svc.reserve(itemId, qty); }
}

// Tomorrow — service-level decoupling (microservice, OrderService unchanged)
export class HttpInventoryAdapter implements InventoryPort {
  constructor(private baseUrl: string) {}
  async reserve(itemId: string, qty: number) {
    const r = await fetch(`${this.baseUrl}/reserve`, {
      method: "POST", body: JSON.stringify({ itemId, qty }),
      headers: { "Content-Type": "application/json" },
    });
    return (await r.json()).reserved as boolean;
  }
}

💡Conclusión clave

La mejor arquitectura de microservicios comienza como un monolito bien estructurado. Un monolito mal estructurado se convierte en un monolito distribuido — pagas el impuesto de red pero no obtienes ninguno de los beneficios de aislamiento. Traza las fronteras primero; elige el modo de desacoplamiento después.

🔧 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 mejor arquitectura de microservicios comienza como un monolito bien estructurado. Un monolito mal estructurado se convierte en un monolito distribuido — lo peor de ambos mundos.

✗ Tu versión