StackPractices
intermediate Por Mathias Paulenko

Patrón Business Delegate

Reduce el acoplamiento entre capas de presentación y negocio introduciendo un intermediario que maneja lookup, creación e invocación de servicios de negocio.

Visión general

El Patrón Business Delegate reduce el acoplamiento entre la capa de presentación y la capa de servicios de negocio introduciendo una capa intermediaria. En lugar de que la capa de presentación acceda directamente a servicios de negocio (EJBs, APIs remotas, u objetos de servicio complejos), utiliza un Business Delegate que maneja la búsqueda, creación e invocación del servicio.

Este patrón es particularmente valioso en aplicaciones enterprise donde los servicios de negocio pueden estar distribuidos, cambiar con frecuencia o requerir una inicialización compleja. El Business Delegate también puede guardar resultados en caché, gestionar reintentos y ofrecer una interfaz simplificada a la capa de presentación.

Cuándo Usar

Usa el Patrón Business Delegate cuando:

  • La capa de presentación necesita acceder a servicios de negocio remotos o distribuidos
  • La búsqueda del servicio de negocio es compleja o involucra JNDI, registros de servicios o contenedores DI
  • Quieres guardar en caché las referencias de servicios para evitar búsquedas repetidas
  • La API de la capa de negocio es compleja y necesitas una fachada simplificada para presentación

Cuándo Evitar

  • Aplicaciones monolíticas simples donde presentación y negocio no están separados
  • Cuando una fachada simple, un Context Object o la inyección directa de servicio es suficiente
  • El overhead de una capa adicional no está justificado por la reducción de acoplamiento

Solución

Python

from abc import ABC, abstractmethod
from typing import Optional, Dict, Any

class OrderServiceInterface(ABC):
    @abstractmethod
    def create_order(self, customer_id: str, items: list) -> dict:
        pass

    @abstractmethod
    def get_order(self, order_id: str) -> dict:
        pass


class RemoteOrderService(OrderServiceInterface):
    """Servicio remoto EJB o API simulado"""
    def create_order(self, customer_id: str, items: list) -> dict:
        # Simular llamada de red
        return {"order_id": "ORD-123", "status": "created", "items": items}

    def get_order(self, order_id: str) -> dict:
        return {"order_id": order_id, "status": "shipped"}


class ServiceLocator:
    """Registro central para lookup de servicios"""
    _services: Dict[str, Any] = {}

    @classmethod
    def register(cls, name: str, service: Any):
        cls._services[name] = service

    @classmethod
    def lookup(cls, name: str) -> Any:
        return cls._services.get(name)


class OrderBusinessDelegate:
    """Delegate que simplifica acceso a OrderService"""
    def __init__(self):
        self._service: Optional[OrderServiceInterface] = None

    def _get_service(self) -> OrderServiceInterface:
        if self._service is None:
            self._service = ServiceLocator.lookup("OrderService")
            if self._service is None:
                self._service = RemoteOrderService()
        return self._service

    def create_order(self, customer_id: str, items: list) -> dict:
        try:
            return self._get_service().create_order(customer_id, items)
        except Exception as e:
            # Manejar excepciones remotas, retry logic, etc.
            raise RuntimeError(f"Failed to create order: {e}")

    def get_order(self, order_id: str) -> dict:
        return self._get_service().get_order(order_id)


# Capa de presentación (equivalente servlet/controller)
class OrderController:
    def __init__(self):
        self.delegate = OrderBusinessDelegate()

    def handle_create_order(self, customer_id: str, items: list):
        result = self.delegate.create_order(customer_id, items)
        return f"Order created: {result['order_id']}"


# Uso
ServiceLocator.register("OrderService", RemoteOrderService())
controller = OrderController()
print(controller.handle_create_order("CUST-001", ["item1", "item2"]))

Java

import java.util.*;

interface OrderService {
    Map<String, Object> createOrder(String customerId, List<String> items);
    Map<String, Object> getOrder(String orderId);
}

class RemoteOrderService implements OrderService {
    public Map<String, Object> createOrder(String customerId, List<String> items) {
        Map<String, Object> result = new HashMap<>();
        result.put("order_id", "ORD-123");
        result.put("status", "created");
        result.put("items", items);
        return result;
    }

    public Map<String, Object> getOrder(String orderId) {
        Map<String, Object> result = new HashMap<>();
        result.put("order_id", orderId);
        result.put("status", "shipped");
        return result;
    }
}

class ServiceLocator {
    private static final Map<String, Object> services = new HashMap<>();
    public static void register(String name, Object service) { services.put(name, service); }
    public static Object lookup(String name) { return services.get(name); }
}

class OrderBusinessDelegate {
    private OrderService service;

    private OrderService getService() {
        if (service == null) {
            service = (OrderService) ServiceLocator.lookup("OrderService");
            if (service == null) service = new RemoteOrderService();
        }
        return service;
    }

    public Map<String, Object> createOrder(String customerId, List<String> items) {
        try {
            return getService().createOrder(customerId, items);
        } catch (Exception e) {
            throw new RuntimeException("Failed to create order: " + e.getMessage(), e);
        }
    }

    public Map<String, Object> getOrder(String orderId) {
        return getService().getOrder(orderId);
    }
}

class OrderController {
    private final OrderBusinessDelegate delegate = new OrderBusinessDelegate();

    public String handleCreateOrder(String customerId, List<String> items) {
        Map<String, Object> result = delegate.createOrder(customerId, items);
        return "Order created: " + result.get("order_id");
    }
}

// Uso
ServiceLocator.register("OrderService", new RemoteOrderService());
OrderController controller = new OrderController();
System.out.println(controller.handleCreateOrder("CUST-001", List.of("item1", "item2")));

JavaScript

class RemoteOrderService {
  createOrder(customerId, items) {
    return { order_id: 'ORD-123', status: 'created', items };
  }

  getOrder(orderId) {
    return { order_id: orderId, status: 'shipped' };
  }
}

class ServiceLocator {
  static services = new Map();
  static register(name, service) { this.services.set(name, service); }
  static lookup(name) { return this.services.get(name); }
}

class OrderBusinessDelegate {
  constructor() {
    this.service = null;
  }

  getService() {
    if (!this.service) {
      this.service = ServiceLocator.lookup('OrderService') || new RemoteOrderService();
    }
    return this.service;
  }

  createOrder(customerId, items) {
    try {
      return this.getService().createOrder(customerId, items);
    } catch (e) {
      throw new Error(`Failed to create order: ${e.message}`);
    }
  }

  getOrder(orderId) {
    return this.getService().getOrder(orderId);
  }
}

class OrderController {
  constructor() {
    this.delegate = new OrderBusinessDelegate();
  }

  handleCreateOrder(customerId, items) {
    const result = this.delegate.createOrder(customerId, items);
    return `Order created: ${result.order_id}`;
  }
}

// Uso
ServiceLocator.register('OrderService', new RemoteOrderService());
const controller = new OrderController();
console.log(controller.handleCreateOrder('CUST-001', ['item1', 'item2']));

Explicación

El Business Delegate actúa como proxy y adapter entre la capa de presentación y los servicios de negocio:

  • Capa de Presentación: Usa la simple interfaz del Business Delegate
  • Business Delegate: Maneja lookup de servicio, caching, traducción de excepciones, y retry logic
  • Service Locator: El registro central para encontrar servicios de negocio
  • Business Service: La implementación remota o compleja del servicio real

Este enfoque por capas significa que el código de presentación nunca referencia directamente interfaces remotas, JNDI, o excepciones específicas de servicio. El delegate funciona en la práctica como un proxy con búsqueda, caché y traducción de errores integrados.

La capa de presentación llama al Business Delegate, que resuelve el servicio mediante un Service Locator (con caché y reintentos) e invoca el servicio de negocio remoto.

Variantes

VarianteFuncionalidad adicionalCaso de uso
BasicLookup y delegación simpleApps enterprise estándar
CachingGuarda referencias de servicio en cachéSistemas de alto rendimiento
RetryReintento automático ante fallosServicios remotos poco fiables
AsyncInvocación asíncronaPresentación no bloqueante

Delegate con caché y reintentos

La tabla de variantes no es teórica: un delegate de producción suele guardar la referencia del servicio en caché y reintentar las llamadas remotas con backoff:

import time

class ResilientOrderDelegate:
    """Guarda la referencia del servicio y reintenta llamadas remotas."""
    def __init__(self, max_retries: int = 3):
        self._service = None
        self._max_retries = max_retries

    def _get_service(self):
        if self._service is None:  # se busca una vez y queda en caché
            self._service = ServiceLocator.lookup("OrderService") or RemoteOrderService()
        return self._service

    def create_order(self, customer_id: str, items: list) -> dict:
        delay = 0.05
        for attempt in range(1, self._max_retries + 1):
            try:
                return self._get_service().create_order(customer_id, items)
            except Exception as exc:
                if attempt == self._max_retries:
                    raise RuntimeError("Servicio de pedidos no disponible tras %d intentos" % attempt) from exc
                time.sleep(delay)  # backoff exponencial simple
                delay *= 2

Dos detalles importan aquí. La búsqueda corre una sola vez y la referencia queda en caché, así que las llamadas repetidas no pagan el coste de JNDI o del registro. Y el bucle de reintentos convierte una llamada remota inestable en un resultado o en un único error de dominio: el controller jamás ve una excepción de transporte.

Delegate vs. soluciones adyacentes

EnfoqueRasgo compartidoDiferencia clave
FacadeSimplifica una API complejaOculta la complejidad del subsistema, no lo remoto
Service LocatorEncuentra serviciosSolo un registro: sin política de caché, reintentos ni traducción de excepciones
Inyección de dependenciasInvierte la creación de serviciosConecta colaboradores al arranque; sin búsqueda en runtime ni manejo de errores remotos
Business DelegateSe interpone entre capasAgrupa búsqueda, caché, reintentos y traducción para servicios remotos

Lo que funciona

  • Guarda las referencias de servicio en caché. Evita búsquedas repetidas en JNDI o en el registro.
  • Traduce excepciones. Convierte excepciones remotas/de servicio en errores amigables para presentación.
  • Mantén el delegate ligero. La lógica de negocio pertenece al servicio, no al delegate.
  • Úsalo con Service Locator o DI. El delegate no debería fijar a fuego la creación del servicio.
  • Implementa reintentos para llamadas remotas. Los fallos de red deberían resolverse sin que el usuario lo note.

Errores Comunes

  • Poner lógica de negocio en el delegate. El delegate solo debería rutear y simplificar.
  • No guardar referencias en caché. Las búsquedas repetidas son costosas.
  • Exponer excepciones remotas a la capa de presentación. Siempre traduce a excepciones de dominio.
  • Acoplamiento fuerte a una implementación específica de servicio. Usa interfaces y factories.
  • Usarlo de más para servicios locales. La inyección directa es más simple cuando los servicios viven en el mismo proceso.

Ejemplos del Mundo Real

Java EE / Jakarta EE

Business Delegate fue un patrón core de J2EE. Los session beans eran accedidos vía delegates para ocultar complejidad EJB de servlets y JSPs.

Spring Framework

Aunque la inyección de dependencias de Spring reduce la necesidad de delegates manuales, las capas @Service con @Transactional a menudo actúan como business delegates entre controllers y repositories.

Microservice Gateways

Los API gateways en arquitecturas de microservicios a menudo implementan lógica de Business Delegate, agregando múltiples servicios backend en una única interfaz orientada al cliente.

Véase también

Preguntas frecuentes

Cuál es la diferencia entre Business Delegate y Facade?

Una Facade simplifica la interfaz de un subsistema complejo. Un Business Delegate media específicamente entre presentación y servicios de negocio remotos/distribuidos.

Cómo se relaciona Business Delegate con Service Locator?

El delegate a menudo usa Service Locator para encontrar el servicio de negocio real. Son patrones complementarios.

Es Business Delegate aún relevante con frameworks modernos de DI?

La necesidad de delegates manuales ha disminuido con Spring y CDI, pero el concepto vive en service layers, BFFs, y API gateways.

¿Cuál es la diferencia entre Business Delegate y un BFF (Backend for Frontend)?

Un BFF es un servicio de backend dedicado por tipo de cliente; un Business Delegate es un objeto dentro de la capa de presentación. Ambos aíslan al cliente de la complejidad del servicio: el BFF cruzando una frontera de red, el delegate dentro del mismo proceso.

¿Necesito un Business Delegate con Spring o CDI?

Normalmente no como clase separada: el contenedor ya maneja búsqueda e inyección. El patrón sigue ganando su lugar como capa de cara al servicio cuando las llamadas remotas necesitan caché, reintentos o traducción de errores.