Documentation for a newer release is available. View Latest

Anatomía de una Aplicación Típica de Orquestación IPF

Una aplicación de orquestación IPF consta de varios componentes clave que trabajan juntos para procesar y enrutar mensajes. Este documento describe la arquitectura de una aplicación de orquestación IPF, cubriendo las interacciones entre estos componentes, el flujo de mensajes y cómo se maneja el backpressure.

Componentes principales

Diagram

Componentes de dominio

El componente Domains mostrado en el diagrama anterior abarca componentes definidos por la DSL y generados por MPS que proporcionan la lógica de orquestación dentro del IPF Orchestration Service:

  • Flow Event Sourced Behaviours (ESBs): Forman el núcleo de la lógica de orquestación, implementando máquinas de estados finitos que impulsan los procesos de negocio. Los ESBs son completamente generados por MPS y usan event sourcing, leyendo y persistiendo domain events a través del Akka Persistence Plugin.

  • Input Adapters: Estos componentes envían inputs a los flow ESBs, activando transiciones entre estados de la máquina de estados finitos. Los Input Adapters son completamente generados por MPS.

  • Action Adapters: Definidos por el servicio de orquestación IPF, estos componentes implementan los Action Ports generados por MPS e integran con el Domain a través de esas interfaces. Los Action Adapters son implementados por el equipo de ingeniería que crea el servicio de orquestación.

Componentes de conectividad

  • Receive Connectors: Componentes de las librerías de conectividad del sistema IPF que consumen mensajes enviados por dominios externos. Su función principal en un servicio de orquestación es convertir los mensajes entrantes en Input apropiados y reenviarlos al Input Adapter correspondiente.

  • Send Connectors: Similares a los Receive Connectors, pero diseñados para comunicación saliente. Estos componentes transmiten mensajes a dominios externos.

  • Cache Adapters: Proporcionados por las librerías de caché de IPF, estos componentes normalmente operan delante de Request Reply Send Connectors para cachear resultados de llamadas HTTP. Delegan a los conectores cuando hay un fallo de caché, pero devuelven resultados en caché cuando están disponibles.

Interacción del sistema y flujo de mensajes

Los componentes de IPF interactúan de manera coordinada para procesar mensajes. Comprender este flujo y cómo se aplica el backpressure en todo el sistema es esencial para la solución de problemas, la planificación de capacidad y la optimización del rendimiento.

Proceso de flujo de mensajes

ipf message flow drawio

A continuación se muestra una breve descripción de cada paso en el flujo de mensajes. Ten en cuenta que, debido al sharding de clúster de Akka, los pasos 1-3 y 4-7 probablemente se ejecuten en diferentes nodos del servicio de orquestación IPF.

  1. Los mensajes normalmente entran en un servicio de orquestación IPF a través de un Receive Connector

  2. El conector transforma el mensaje recibido en un Input apropiado y llama al Domain Input Adapter correspondiente

  3. El único propósito del adapter es convertir el input en un command y enviarlo al Flow ESB apropiado, que puede residir en un nodo de servicio diferente

  4. Cuando recibe el command, el Flow ESB primero verifica si el command debe aplicarse. Si la verificación pasa, se persiste un event usando el persistence plugin. Si la verificación falla, se omiten los pasos posteriores.

  5. (Opcional) Akka Persistence Plugin convierte los events en comandos de escritura de base de datos y se comunica con la base de datos

  6. (Opcional) Si hay actions configuradas (side effects) que deban dispararse tras la persistencia del event, el Flow ESB las activa en este punto a través del Action Adapter correspondiente.

    • Si la implementación de la action modela una interacción con un sistema externo, se puede usar un Send Connector para ejecutarla, como se muestra en el paso 7. La elección del Send Connector se basa en el tipo de action que se ha modelado:

      • Se usa una Request-Response action — Se usa un Send Connector o un Request Reply Send Connector sin caché para enviar un mensaje. Cualquier posible respuesta a ese mensaje desde el dominio externo se recibirá mediante un Receive Connector.

      • Se usa una Domain Function action — Se llama a un Request Reply Send Connector potencialmente cacheado, lo que significa que primero se consulta un Cache Adapter configurado.

        • En un fallo de caché, la llamada al Request Reply Send Connector continúa y el resultado de la operación se almacena en la caché para uso futuro.

        • En un acierto de caché, se omite el paso 7.

    • Si la implementación de la action representa un proceso que se maneja completamente dentro de los límites del dominio, no se requiere Send Connector y el Action Adapter convierte la respuesta en un Input apropiado y llama a su Input Adapter, continuando el flujo desde el paso 3.

    • Si no hay actions que disparar, también se omite el paso 7.

  7. (Opcional) El connector convierte los datos de la action en un formato que el dominio externo espera y los envía usando el transporte configurado.

Backpressure

El backpressure es un medio de control de flujo y una forma para que los consumidores de datos notifiquen a un productor su disponibilidad actual, ralentizando efectivamente al productor upstream para igualar sus velocidades de consumo. La mayoría de las librerías de reactive streams ofrecen a los desarrolladores varias aproximaciones (que a veces pueden combinarse) para usar cuando se encuentra backpressure:

  • Bufferizar mensajes

  • Reducir la velocidad (throttling) del procesamiento

  • Shed load (descartar mensajes)

  • Tomar el último (head) mensaje

Usualmente, el backpressure aplica dentro de un único proceso de SO (en nuestro caso, una JVM) y se menciona con mayor frecuencia en el contexto de reactive streams. Algunos protocolos de mensajería sí soportan backpressure como parte de su especificación, permitiendo que las señales salgan de los límites de un único proceso (p. ej., AMQP flow control).

Backpressure en Akka

IPF Connectors están construidos sobre Akka Streams, una implementación de reactive streams, que viene con mecanismos de backpressure incorporados.

Los actores de Akka se comunican por paso de mensajes asíncrono, que por defecto no ofrece backpressure. Sin embargo, se puede añadir backpressure aplicando el ask pattern, obligando así a los usuarios de cualquier actor (lo que incluye los IPF flows) a esperar una respuesta antes de continuar.

Para los flows, Akka Persistence solo permitirá un número configurable de flows para iniciarse o rehidratarse concurrentemente. Esto, en combinación con el ask pattern mencionado, actúa como una barrera protectora alrededor del event store, propagando cualquier backpressure desde la base de datos hacia arriba.

Backpressure en DB

IPF usa el reactive streams MongoDB driver, que viene con soporte de backpressure incorporado. El cliente reactivo de MongoDB reduce la velocidad ante backpressure, y este backpressure se propaga a IPF Connectors y Akka Persistence.

Backpressure en Connectors

Como se mencionó antes, IPF Connectors están construidos usando Akka Streams, haciendo que el backpressure sea una característica incorporada.

Todas las etapas del conector reducen la velocidad del consumo bajo backpressure por defecto, aunque ciertas etapas ofrecen otras opciones:

  • Source.queue se usa principalmente en Send Connectors para encolar mensajes para enviar. Hace buffer de solicitudes para enviar hasta un límite, luego reduce la velocidad de los productores hasta un límite. Una vez que ambos límites se superan, comienza a descartar mensajes.

  • Flow.mapAsync se usa en todos los tipos de conectores para realizar diversos trabajos potencialmente de larga duración: mapeo, logging, correlación, agregación a dead letter queues, etc. Puede verse como proporcionando un buffer de solicitudes ejecutándose concurrentemente, tras lo cual reduce la velocidad del upstream.

Backpressure en IPF (Todo junto)

ipf backpressure flow drawio

Como se explica en la sección de flujo de mensajes, todos los componentes de IPF están estrechamente vinculados, lo que hace que sus mecanismos de backpressure se combinen. En la práctica, los Receive Connectors sirven como el principal cuello de botella en el servicio de orquestación IPF, donde el backpressure de las dependencias downstream finalmente se manifiesta. Esto significa que la velocidad a la que el Receive Connector consume y procesa trabajo está determinada por la velocidad de procesamiento de todo el flujo de mensajes, y no es más rápida que su paso más lento.

El backpressure fluye a través del servicio como sigue:

  1. Un External System crea un mensaje y lo hace llegar vía el transporte que elija. El transporte elegido influye en si las señales de backpressure del servicio de orquestación llegan al sistema externo. HTTP puede shed load devolviendo errores 503 en backpressure, AMQP soporta ralentizar productores, mientras que Kafka y JMS simplemente actúan como buffers.

  2. Cada Receive Connector tendrá una o más etapas con un pequeño buffer de mensajes que pueden procesarse en paralelo. Cuando el buffer está lleno, el conector aplicará backpressure y dejará de consumir trabajo nuevo. Los mensajes solo se eliminan del buffer cuando su procesamiento se completa (con éxito o no). Dependiendo de la configuración de resiliencia del conector, el procesamiento de un mensaje fallido puede reintentarse un número configurable de veces hasta que se alcance el máximo de intentos, o hasta que expire la duración de timeout configurada.

  3. Un Input Adapter no ofrece mecanismos de backpressure propios y en su lugar propaga el backpressure downstream esperando confirmación — en forma de un mensaje de resultado de procesamiento satisfactorio — de que los pasos 3-7 se han completado con éxito. Los adapters vienen con su propio RetrySupport — una llamada fallida (reintetable) al Flow ESB se reintentará un número configurable de veces antes de que el fallo se propague al conector.

  4. Cada llamada al Flow ESB desde un Input Adapter usará el soporte de Akka para mensajería de clúster con localización transparente, y esperará una cantidad de tiempo configurada para que se reciba el mensaje de resultado del procesamiento antes de expirar y señalar al RetryStrategy del adapter que el intento ha fallado. Los actores Flow ESB procesan commands uno por uno y hacen buffer de mensajes pendientes en sus colas de mensajes. Si el Flow ESB determina que el command no debe aplicarse (p. ej., es inválido para el estado actual, un duplicado, etc.), se envía un resultado de procesamiento al Input Adapter y se omiten los pasos posteriores.

  5. La velocidad a la que se procesan los commands depende directamente de la velocidad a la que el Akka Persistence Plugin puede persistirlos. Una vez que el event se persiste, el Flow ESB queda libre para aceptar nuevos commands.

  6. Todas las actions configuradas que el Flow ESB dispara se llaman de manera asíncrona y en paralelo. El resultado del procesamiento se envía al Input Adapter del paso 2 solo después de que todas las actions se hayan completado.

  7. Dependiendo del tipo de action y su implementación, aplican diferentes reglas de backpressure:

    • Decisions no interactúan con sistemas externos, pero dan lugar a una ejecución encadenada que efectivamente dispara los pasos 3-7 y propaga su backpressure upstream hacia el Receive Connector.

    • Domain Functions pueden requerir interacciones externas, tras lo cual sigue una ejecución encadenada, disparando efectivamente los pasos 3-7 y propagando su backpressure upstream hacia el Receive Connector.

    • Notifications y Request Response actions normalmente requieren interacciones externas, pero típicamente no disparan ejecución encadenada a menos que interactúen con un flujo separado. Como resultado, solo el backpressure de los Send Connectors se propagará al Receive Connector.

    • A menos que sean remotos o distribuidos, los Cache Adapters normalmente no aplican backpressure: acceder a la caché es una operación en memoria.

    • Tanto el Send Connector simple como el Request Reply Send Connector proporcionan un buffer para mensajes que se envían en paralelo. Una vez que el buffer está lleno, reducirá la velocidad y luego empezará a descartar mensajes.

    • Ambos tipos de Send Connector soportan reintentos y timeouts configurables, que se incluirán en las reducciones de velocidad por backpressure.