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
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
Portsgenerados por MPS e integran con elDomaina 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
Inputapropiados y reenviarlos alInput Adaptercorrespondiente. -
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 Connectorspara 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
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.
-
Los mensajes normalmente entran en un servicio de orquestación IPF a través de un
Receive Connector -
El conector transforma el mensaje recibido en un
Inputapropiado y llama alDomainInput Adaptercorrespondiente -
El único propósito del adapter es convertir el input en un command y enviarlo al
Flow ESBapropiado, que puede residir en un nodo de servicio diferente -
Cuando recibe el command, el
Flow ESBprimero 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. -
(Opcional)
Akka Persistence Pluginconvierte los events en comandos de escritura de base de datos y se comunica con la base de datos -
(Opcional) Si hay actions configuradas (side effects) que deban dispararse tras la persistencia del event, el
Flow ESBlas activa en este punto a través delAction Adaptercorrespondiente.-
Si la implementación de la action modela una interacción con un sistema externo, se puede usar un
Send Connectorpara ejecutarla, como se muestra en el paso 7. La elección delSend Connectorse basa en el tipo de action que se ha modelado:-
Se usa una Request-Response action — Se usa un
Send Connectoro unRequest Reply Send Connectorsin caché para enviar un mensaje. Cualquier posible respuesta a ese mensaje desde el dominio externo se recibirá mediante unReceive Connector. -
Se usa una Domain Function action — Se llama a un
Request Reply Send Connectorpotencialmente cacheado, lo que significa que primero se consulta unCache Adapterconfigurado.-
En un fallo de caché, la llamada al
Request Reply Send Connectorcontinú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 Connectory elAction Adapterconvierte la respuesta en unInputapropiado y llama a suInput Adapter, continuando el flujo desde el paso 3. -
Si no hay actions que disparar, también se omite el paso 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.queuese usa principalmente enSend Connectorspara 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.mapAsyncse 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)
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:
-
Un
External Systemcrea 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. -
Cada
Receive Connectortendrá 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. -
Un
Input Adapterno 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 propioRetrySupport— una llamada fallida (reintetable) alFlow ESBse reintentará un número configurable de veces antes de que el fallo se propague al conector. -
Cada llamada al
Flow ESBdesde unInput Adapterusará 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 alRetryStrategydel adapter que el intento ha fallado. Los actoresFlow ESBprocesan commands uno por uno y hacen buffer de mensajes pendientes en sus colas de mensajes. Si elFlow ESBdetermina 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 alInput Adaptery se omiten los pasos posteriores. -
La velocidad a la que se procesan los commands depende directamente de la velocidad a la que el
Akka Persistence Pluginpuede persistirlos. Una vez que el event se persiste, elFlow ESBqueda libre para aceptar nuevos commands. -
Todas las actions configuradas que el
Flow ESBdispara se llaman de manera asíncrona y en paralelo. El resultado del procesamiento se envía alInput Adapterdel paso 2 solo después de que todas las actions se hayan completado. -
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 Connectorsse propagará alReceive Connector. -
A menos que sean remotos o distribuidos, los
Cache Adaptersnormalmente no aplican backpressure: acceder a la caché es una operación en memoria. -
Tanto el
Send Connectorsimple como elRequest Reply Send Connectorproporcionan 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 Connectorsoportan reintentos y timeouts configurables, que se incluirán en las reducciones de velocidad por backpressure.
-