Saltar al contenido
Módulo 3 de 7Básico

Actores y direcciones: driving vs. driven

Primario o secundario, driving o driven: la distinción no es por dónde entran los datos, sino quién inicia la conversación. El test que conduce tu aplicación, el correo que solo responde y los casos que engañan a todo el mundo.

Son las 8:03 de la mañana en la cafetería y ocurren dos conversaciones casi al mismo tiempo. Primera: la barista toca «Confirmar pedido» en la tablet y la aplicación despierta, verifica el pedido y lo confirma. Segunda: la aplicación, por su cuenta, le pide al servicio de correo que avise al cliente que su encargo va en camino. Misma frontera, mismo hexágono… pero las flechas apuntan en direcciones opuestas. Reproduce ambas conversaciones y fíjate en quién da el primer paso en cada una:

Confirmar pedido (driving)

  1. Barista (tablet)Alguien de afuera necesita algo: la barista toca «Confirmar pedido» y saca a la aplicación del reposo.
  2. AdaptadoresHandler HTTPEl adaptador traduce el POST en una llamada agnóstica al caso de uso.
  3. DominioConfirmarPedido.ejecutarEl caso de uso orquesta la operación dentro del hexágono.
  4. DominioPedido.confirmar()La entidad aplica su regla: un pedido sin líneas no se confirma.

Avisar por correo (driven)

  1. DominioConfirmarPedido.ejecutarEsta vez es la aplicación quien inicia la conversación: necesita avisar al cliente.
  2. PuertosNotificadorDePedidos (puerto)El núcleo habla por su contrato, sin saber quién está del otro lado.
  3. AdaptadoresSmtpNotificadorDePedidosEl adaptador traduce el contrato agnóstico al dialecto del correo.
  4. Servidor SMTPEl servidor de correo solo responde: nunca inicia nada.

Esa diferencia de dirección tiene nombre propio en el patrón —driving y driven— y es, con distancia, el concepto que más se malinterpreta de la arquitectura hexagonal. Este módulo existe para que no te pase.

Actores: quién habla con tu aplicación

Cockburn dibujó su hexágono con dos regiones. Garrido de Paz lo resume así sobre el diagrama canónico:

“Actors on the left/top side are Drivers, or Primary Actors. The interaction is triggered by the actor.”

“Actors on the right/bottom side are Driven Actors, or Secondary Actors. The interaction is triggered by the application.”

Un actor primario (driving) es cualquiera que necesita algo de la aplicación y la saca del reposo: la barista en la tablet, una app de delivery que envía pedidos, un script nocturno, otro sistema. Un actor secundario (driven) es cualquiera a quien la aplicación necesita para completar su trabajo: la base de datos que guarda los pedidos, el servicio de correo que avisa, la pasarela que cobra. Los primarios están a un lado del hexágono por convención de dibujo; lo que los define no es la posición, sino la frase clave de ambas citas: quién dispara la interacción.

La pregunta correcta: ¿quién inicia la conversación?

El error clásico es traducir driving/driven como “entrada/salida”, y es un mal mapa. Piénsalo con el repositorio de la cafetería: cuando el caso de uso llama a buscar(id), los datos del pedido entran a la aplicación desde la base de datos. ¿Convierte eso a PostgreSQL en un actor “de entrada”? No: la conversación la inició el núcleo, la base de datos solo respondió. Los datos fluyen en ambos sentidos en los dos lados del hexágono; lo que nunca cambia es quién habló primero.

Por eso la única pregunta confiable es: ¿quién inicia la conversación? Si el de afuera toca la puerta, es driving. Si el núcleo marca el número, es driven. Grábatela: el minijuego de este módulo está diseñado para tenderte todas las trampas conocidas.

Adaptadores driving y driven: usar vs. implementar

La dirección de la conversación también parte en dos a los adaptadores del módulo anterior, y la distinción técnica es elegante. Citas textuales de Hexagonal Me:

“A driver adapter uses a driver port interface, converting a specific technology request into a technology agnostic request to a driver port.”

“A driven adapter implements a driven port interface, converting the technology agnostic methods of the port into specific technology methods.”

Ahí está la asimetría que dejamos pendiente: el adaptador driving usa un puerto del núcleo; el adaptador driven implementa un puerto del núcleo. Míralo en el código, frente a frente:

El handler HTTP

adaptadores/http_pedidos.rs — adaptador drivingAdaptadores
/// Traduce un POST /pedidos/42/confirmar en una llamada
/// agnóstica al caso de uso. Usa el núcleo; no lo implementa.
async fn confirmar_pedido(
    State(app): State<Aplicacion>,
    Path(id): Path<PedidoId>,
) -> Respuesta {
    match app.confirmar_pedido.ejecutar(id) {
        Ok(()) => Respuesta::sin_contenido(),
        Err(error) => Respuesta::desde(error),
    }
}

No implementa ningún trait del dominio: recibe un POST, lo traduce y llama al caso de uso.

Usa el puerto — adaptador driving

El caso de uso

dominio/confirmar_pedido.rs — el caso de usoDominio
/// Caso de uso: confirmar un pedido y avisar al exterior.
pub struct ConfirmarPedido<R: PedidoRepository, N: NotificadorDePedidos> {
    repositorio: R,
    notificador: N,
}

impl<R: PedidoRepository, N: NotificadorDePedidos> ConfirmarPedido<R, N> {
    pub fn ejecutar(&self, id: PedidoId) -> Result<(), ErrorDeConfirmacion> {
        let mut pedido = self.repositorio.buscar(id)?; // puerto driven
        pedido.confirmar()?;                           // regla del dominio
        self.repositorio.guardar(&pedido)?;            // puerto driven
        self.notificador.pedido_confirmado(&pedido)?;  // puerto driven
        Ok(())
    }
}

Declara con genéricos los dos puertos driven que necesita; el repositorio Postgres no llama a nadie, espera a que el núcleo lo invoque a través del trait que implementa.

Implementa el puerto — el driven está del otro lado

Cuenta las flechas: el handler llama al caso de uso (entra al hexágono), el caso de uso llama al repositorio y al notificador (sale del hexágono). Tres conversaciones, un solo hilo de ejecución, y cada frontera cruzada por un contrato.

El test es un actor primario

Aquí el patrón muestra su carta ganadora. Recuerda la intención de Cockburn del módulo 1: que la aplicación pueda ser conducida “por usuarios, programas, tests automatizados o scripts batch” por igual. Un test que invoca ConfirmarPedido::ejecutar no es un ciudadano de segunda: es un actor primario con todas las de la ley, que entra por el mismo puerto que el handler HTTP. En el artículo original, Cockburn señala para cada lado su sustituto natural de pruebas: el arnés de tests como adaptador driving (“FIT is the natural test adapter for primary actors”) y los objetos simulados como adaptadores driven (“mock objects are the natural test adapter for secondary actors”).

La simetría es preciosa: puedes sustituir al humano por un test en el lado driving, y a la base de datos por un doble en memoria en el lado driven. Compáralo con la primera conversación de las 8:03 — la flecha entra por el mismo lugar:

  1. Test automatizadoEl test inicia la conversación, igual que la barista en la tablet.
  2. DominioConfirmarPedido.ejecutarMismo puerto de entrada que el handler HTTP: el núcleo ejecuta exactamente el mismo código sin distinguir quién lo conduce.

De eso vivirá el módulo de testing, más adelante en el curso.

Los casos que engañan

Tres trampas clásicas, para que llegues advertido al minijuego:

El cron nocturno. “Es infraestructura, seguro es driven”. Falso: el scheduler que dispara el cierre de caja a las 11 pm inicia la conversación igual que la barista. La pregunta no es qué tan fierro se ve, sino quién habla primero.

La cola de mensajes. No es driving ni driven “en general”: depende de la conversación. El consumidor que recibe pedidos de la app de delivery dispara casos de uso — driving. El publicador con el que el núcleo emite «pedido confirmado» es invocado por la aplicación — driven. Misma tecnología, dos roles.

El sistema externo grande. La pasarela de pagos impone respeto, pero el tamaño no da la iniciativa. Cuando el núcleo le pide cobrar, es driven; cuando su webhook avisa que el cobro se acreditó, esa conversación la inicia ella: driving. Un mismo sistema puede jugar en ambos lados del hexágono, en conversaciones distintas.

Minijuego: driving o driven

Diez dependencias del sistema de la cafetería. Para cada una, la única pregunta que importa: ¿quién inicia la conversación? Las trampas de la sección anterior están todas adentro, más alguna sorpresa.

Lo que viene

Ya sabes quién habla con quién y en qué dirección. La siguiente pregunta es de fontanería: si el caso de uso solo conoce traits, ¿en qué momento y en qué lugar se decide que PedidoRepository será Postgres y NotificadorDePedidos será SMTP? La respuesta tiene nombre propio —composition root— y un único lugar legítimo donde vivir. Es el tema del próximo módulo.

Fuentes

Práctica

Practica lo aprendido

Tarjetas de repaso

Evaluación del módulo