Cada vez son más las empresas que apuestan por arquitecturas basadas en microservicios. Dichos microservicios están fuertemente especializados, por lo que comúnmente se presenta la necesidad de realizar orquestaciones de los mismos para cubrir una funcionalidad de negocio.

Como respuesta a esta necesidad, Netflix ha liberado recientemente Conductor, un nuevo producto dentro del ecosistema Netflix OSS. Este producto implementa un orquestador de flujos que corre en entornos cloud, implementando cada una de las tareas mediante microservicios. Hoy en el blog vamos a ver cómo funciona.

Orquestación de Microservicios con Conductor

Conductor implementa la orquestación de llamadas a múltiples microservicios, de forma que el consumidor obtenga toda la funcionalidad que necesita sin tener que realizar multitud de llamadas que no le aportan ningún valor como pueden ser insertar un acceso en la tabla de auditoría, o consultar el identificador interno de sus cuentas cuando ya se ha logado en el sistema con su usuario y contraseña y desea el saldo total para mostrarlo por pantalla.

El objetivo de Conductor es ofrecer esta funcionalidad, facilitando el control y la visualización de las interacciones entre los microservicios. Por definición, la orquestación de microservicios que se propone es asíncrona, pudiéndose realizar de forma síncrona en caso de ser necesario.

Netflix ha estado utilizando Conductor durante un año, procesando 2.6 millones de procesos de todo tipo. Además, una característica muy interesante es que se ha utilizado en flujos con una alta complejidad de procesamiento como pueden ser los procesos de ingestión de información desde proveedores de datos o de codificación de videos y el almacenamiento de los mismos.

¿Cómo lo hace?

Conductor está basado en la siguiente arquitectura:

API

El acceso al servidor se realiza vía balanceador HTTP o bien usando un producto de Discovery (Eureka en caso de Netflix OSS).

Servicio

La capa de servicios se basa en microservicios implementados en Java. Dado su carácter stateless se pueden desplegar múltiples servidores para escalar y garantizar la disponibilidad y respuesta del sistema.

Persistencia

Conductor está preparado para funcionar con distintos modos de persistencia:

Conductor también permite usar otros backends implementando las interfaces de acceso a base de datos (DAO). Podrían realizarse implementaciones de alguna de las capas, por ejemplo la caché o las colas, dejando la implementación por defecto de la persistencia de los datos de ejecución.

Configuración

La configuración se realiza mediante un fichero de configuración config.properties, que se pasa como parámetro al arrancar el servidor.

¿Qué aporta Conductor?

La razón de ser de Conductor es realizar la orquestación de microservicios facilitando la creación de flujos de trabajo.

Dichos flujos de trabajo se diseñan de forma sencilla en formato JSON, utilizando diferentes tipos de tareas. Si estás acostumbrado a la automatización de procesos, echarás de menos el editor gráfico, ya que Conductor no lo tiene (ni para flujos ni para la definición de tareas), aunque sí dispone de un visor gráfico de los flujos. Esta característica hace que los flujos implementados sean fácilmente revisables por gente de negocio, siendo necesario un mínimo conocimiento técnico para su creación y modificación.

Tareas

Las tareas deben registrarse en el motor previamente a su uso en algún flujo. Este registro puede realizarse de forma programática o bien utilizando el API REST de gestión Metadata.

Las tareas son de dos tipos, principalmente:

  1. Fork: crea una bifurcación en paralelo.
  2. Fork_join_dynamic: similar al fork, pero realiza la paralelización en función de la expresión de entrada.
  3. Join: realiza un join de un flujo previamente bifurcado en un fork o fork_join_dynamic.
  4. Decide: condicional, de forma similar a un “case switch”.
  5. Sub_workflow: arranca un flujo como una tarea. El flujo queda detenido hasta que el subflujo termina.
  6. Wait: introduce un punto de parada asíncrono en el flujo. Se mantiene en estado in_progress hasta que se actualiza con estado completed o failed.
  7. HTTP: ejecuta la llamada de un microservicio a través de HTTP. Dicha invocación se regirá por la política de reintentos declarada al definir la tarea.

Además de las tareas mencionadas, pueden implementarse nuevas tareas de tipo system con un comportamiento personalizado según la necesidades del proyecto. Para ello habría que extender com.netflix.conductor.core.execution.tasks.WorkflowSystemTask e instanciar la nueva clase mediante patrón Eager Singleton en el startup.

Las tareas pueden tener distintas políticas de reintento en caso de fallo:

Flujos

Los flujos de Conductor son flujos en formato JSON, que hacen referencia a las tareas definidas previamente.

Intercambio de datos

Conductor permite implementar de forma sencilla el intercambio de datos entre las distintas actividades del flujo. Existen dos contextos de datos: workflow y task. De esta forma, el flujo tendrá unos datos de entrada, que podrá propagar a las distintas actividades del flujo a través de los parámetros de entrada de las tareas. También existen datos de salida, tanto a nivel de tarea como de flujo.

El binding de los datos se realizará mediante el uso de JSONPath, que implementa la mayoría de funcionalidad de XPath para DSL JSON. La sintaxis será la siguiente:


${SOURCE.input/output.JSONPath}

Contexto de Ejecución

Conductor sigue un modelo basado en comunicaciones RPC, de forma que los workers corren en máquinas distintas a las del servidor Conductor. De esta forma, son los propios workers los que, vía HTTP, consultan al servidor las tareas pendientes de ejecución. Tras realizar la acción implementada en dicho worker, informan al servidor de la finalización de la tarea, para que continúe el flujo de trabajo.

Dicho modelo proporciona las siguientes características:

¿Qué pasa con los servicios ya existentes o fuera de mi control?

Una pregunta que puede realizarse es qué pasa si existe la necesidad de invocar a microservicios ya existentes, o microservicios que están fuera de mi control y por tanto no puedo implementar con la funcionalidad requerida por los workers.

En este caso existen dos alternativas:

  1. Implementar un worker que se encargue de realizar la llamada al microservicio existente y devolver la información.
  2. Implementar invocación mediante una tarea de tipo HTTP. El problema de esta opción es que dicha implementación se orienta a un modo tradicional en el que el servidor pasa a ser un punto de fallo en la llamada HTTP, además de ser un posible cuello de botella ante un gran número de tareas de tipo HTTP con altos tiempos de respuesta.

Instalación

La instalación de Conductor requiere:

  1. Base de datos: Dynomite
  2. Motor de Indexación: Elasticsearch 2.X
  3. Servidor web: TomCat, Jetty o similar, corriendo sobre JDK 1.8+.

El código podemos encontrarlo aquí.

Para instalarlo se deben seguir los siguientes pasos:


git clone git@github.com:Netflix/conductor.git


cd server
../gradlew server


cd ui
gulp watch

Pasos para iniciar y ejecutar un flujo


POST /workflow/{nombreFlujo}
{
 ... // dataInput
}

a) Hacer poll del tipo de tarea.


GET /tasks/poll/batch/{tipoDeTarea}

b) Realizar las acciones necesarias.

c) Actualizar el estado de la tarea a “completed”.


POST /tasks
{
…
“outputData” : { ... },
“taskStatus”: “COMPLETED”
         }

Conclusión

Como se puede ver, Netflix sigue facilitándonos la vida al proporcionarnos componentes que dan respuesta a las necesidades que nos encontramos diariamente en el desarrollo de aplicaciones basadas en microservicios. ¡Ahora sólo queda ponernos manos a la obra e integrarlo en nuestro stack tecnológico para futuros proyectos!

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.