Command Pattern

Intención y problema

¿Cómo representar una solicitud u operación como una entidad separada del objeto que la invoca? Command convierte una petición en algo que puede suministrarse, conservarse y ejecutarse mediante una colaboración explícita. Pertenece a behavioral-patterns.

La intención GoF de encapsular solicitudes se recoge en la síntesis académica de CMU sobre Command. El ejemplo de cancelación desarrollado aquí es propio. Importa separar request issuer y request execution, no escribir una clase que casualmente tenga Execute().

Un botón y una tarea programada pueden solicitar cancelar una orden sin conocer todos los detalles de su ejecución. CancelOrderCommand representa esa solicitud y permite que distintos invokers trabajen con una conversación común. Las reglas que determinan si la orden puede cancelarse siguen necesitando un propietario claro.

Forces y solicitud manipulable

Queremos desacoplar quién inicia una operación de quién la realiza. También queremos conservar visibilidad sobre datos, autoridad, resultado y lifetime. Una abstracción demasiado genérica puede ocultar decisiones importantes; una clase por cada llamada puede fragmentar código sencillo.

Representar la solicitud permite, según diseño, parameterization, deferred execution, scheduling, queueing, logging, recording history, composición, retry y undo/redo. Son posibilidades, no capacidades obligatorias. Command Pattern no equivale a queue ni background job. Un Invoker puede ejecutar inmediatamente y en el mismo thread.

La solicitud puede necesitar identidad o lifecycle propio cuando interesa registrar quién la pidió, cuándo se admite o si todavía es válida. El patrón no impone un identificador persistente ni un sistema de estados para todas las instancias.

Estructura y colaboración

Client assembles request
    ↓
Invoker → Command → Receiver
            ↑
       ConcreteCommand
Participante clásicoResponsabilidad en el ejemplo
ClientEnsambla CancelOrderCommand con datos y Receiver.
InvokerSolicita ejecutar mediante el contrato común.
CommandExpresa la conversación de ejecución.
ConcreteCommandConserva lo necesario y vincula petición con acción.
ReceiverRealiza la capacidad solicitada y protege sus reglas.

Client aquí es un rol de ensamblaje, no necesariamente el usuario ni el frontend. Invoker puede ser un botón, una colección de acciones o un scheduler. Receiver puede ser la orden o un servicio significativo. Los roles no exigen siempre cinco clases separadas.

En una realización clásica, el Command conserva Receiver y parámetros; execute delega la acción y comunica resultado. Otra variante representa la solicitud como datos y asocia ejecución mediante un handler. Debe explicarse esa colaboración: un DTO terminado en Command, aislado de su intención y ejecución, no demuestra el patrón GoF.

Frente a una llamada normal

order.Cancel()
 
CancelOrderCommand(order, cancellationReason)
    → invoker.execute(command)

Si solo necesitamos cancelar ahora, la llamada directa puede ser más clara. La segunda organización se justifica si la solicitud debe registrarse, programarse, decorarse, despacharse genéricamente o conservar información para otra operación.

Crear un Command para cada método añade navegación, ensamblaje y tipos sin beneficio automático. pattern-selection ayuda a contrastar una presión real con ceremony. Tener varios botones tampoco obliga a una infraestructura genérica si pueden compartir una función pequeña.

La ejecución diferida cambia obligaciones. Una orden válida al crear la solicitud puede estar Shipped cuando se ejecuta. Capturar una referencia o datos no congela el mundo. El Receiver debe comprobar condiciones vigentes; state-pattern organiza comportamiento ligado al lifecycle cuando esa presión lo justifica.

GoF Command y CQRS Command

GoF Command encapsula una solicitud como objeto o valor para separar invocación y ejecución. Un CQRS Command expresa intención de cambiar estado dentro de una separación entre actualización y consulta. Fowler caracteriza CQRS por esa separación de modelos.

Los conceptos pueden solaparse: una petición de modificación puede utilizar una colaboración Command. Pero adoptar commands en CQRS no demuestra automáticamente los participantes GoF, y ejecutar un Command desde un menú no establece CQRS.

El nombre compartido debe desambiguarse por intención y scope. Tampoco una interface con Execute exige separar databases, crear un query model o utilizar mensajería distribuida. Esas decisiones necesitan razones adicionales.

Command object y command message

Un objeto en proceso puede conservar una referencia viva al Receiver. Un mensaje distribuido debe expresar datos transportables para que otro proceso interprete la intención. El Command Message de Hohpe y Woolf aborda ese contexto de integración.

La distribución agrega serialization, transport, versioning, authentication y contratos de resultado. También exige analizar ordering, duplicate delivery y delivery-guarantees. Una referencia en memoria no puede suponerse válida en otro proceso.

Command Pattern no equivale a command message. Pueden representar una misma petición de negocio en fronteras distintas, con obligaciones diferentes. reliable-messaging permite seguir esas obligaciones sin atribuirlas al patrón local.

Repetir la ejecución tampoco es automáticamente seguro. retry describe la política de repetición y idempotency permite examinar efectos duplicados. Una solicitud cuyo primer resultado es incierto puede haber producido ya el efecto, aunque el invoker todavía no lo sepa.

Undo, redo y compensación

Command se asocia históricamente con undo porque puede conservar datos de una operación y relacionarlos con una acción inversa. En un editor, una inserción puede guardar posición y texto necesarios para retirar lo agregado.

Ese contrato necesita condiciones: otra modificación puede invalidar la posición, y repetir undo no debería borrar contenido ajeno. Redo puede significar reaplicar una intención o restaurar un resultado anterior; elegirlo depende del modelo, no del método disponible.

No todo Command es reversible. Enviar email, cobrar una tarjeta externa o eliminar un recurso puede requerir una acción compensatoria con significado de negocio, o no admitir un undo práctico. Un reembolso no borra que existió un cobro.

saga-pattern trata compensación y coordinación de efectos en otro scope. Undo local y compensación distribuida no son equivalentes. Ninguno se obtiene simplemente agregando un método inverso con el nombre correcto.

Composición, decorators y lenguaje

Metrics(
  Logging(
    Authorization(
      Command
    )
  )
)

Un Command puede ofrecer un contrato decorable para responsabilidades adicionales. Fowler desarrolla un ejemplo específico en Decorated Command. decorator-pattern explica por qué orden y conservación del contrato importan.

En el ejemplo propio, medir por fuera de Authorization puede registrar también solicitudes rechazadas; medir por dentro puede registrar solo ejecuciones admitidas. La política de logging y errores debe ser explícita. Si la ejecución se difiere, una autorización previa puede necesitar volver a comprobarse.

Una command pipeline no demuestra que cada middleware sea GoF Decorator. Agrupar Commands tampoco garantiza atomicidad: si uno falla después de otros, la composición necesita decidir qué resultado y qué recuperación ofrece.

Funciones de primera clase, closures y delegates pueden representar acciones con menos clases. Datos inmutables con una función de ejecución asociada pueden hacer explícita la solicitud. Las capturas de referencias y recursos mantienen problemas de lifetime; reducir ceremony no vuelve transportable o reversible la acción.

Consecuencias, aplicabilidad y misuses

Command permite reutilizar invokers y tratar solicitudes como entidades cuando esa necesidad existe. Facilita pruebas de ejecución y de políticas aplicadas alrededor, pero agrega indirection, almacenamiento de datos y reglas sobre su vigencia.

Las pruebas relevantes verifican resultado, fallos, autoridad, condiciones cambiadas y repetición bajo el contrato acordado. Probar únicamente que execute llama a otro método no demuestra el beneficio ni seguridad de la operación.

Es misuse crear objetos para llamadas triviales, presentar dispatch síncrono como arquitectura asíncrona o concentrar toda la application/domain layer en Commands gigantes. Un Receiver anémico y Commands omniscientes pueden desplazar reglas sin intención clara; no toda coordinación pertenece a la solicitud.

mediator-pattern puede coordinar o despachar peticiones, mientras Command representa la solicitud. design-pattern y design-patterns recuperan contexto y comparación. La decisión útil conserva una petición manipulable por razones concretas, sin convertir cada llamada en infraestructura.