«Patrones de diseño de tareas» y mejores prácticas de Talend

«Patrones de diseño de tareas» y mejores prácticas de Talend

  • Dale Anderson
    Dale Anderson is a Customer Success Architect at Talend. Over a 30 year career, Mr. Anderson has gained extensive experience in a range of disciplines including systems architecture, software development, quality assurance, and product management and honed his skills in database design, modeling, and implementation, as well as data warehousing and business intelligence. A keen advocate for an appropriate temperance of the software workflow process, he has applied his innovations to the often overlooked lifecycle of a database. The Database Development Lifecycle Mr. Anderson has developed incorporates data modeling best practices to address the complexities involved in modeling adaptations, multi-environment fulfilments, fresh installations, schema upgrades, and data migrations for any database implementation.
 

Los desarrolladores de Talend de todo el mundo, desde los principiantes a los más avezados, suelen lidiar con la misma pregunta: «¿Cuál es la mejor forma de escribir mi tarea?» Sabemos que debería ser eficiente, fácil de leer, de escribir y, ante todo (en la mayor parte de casos), fácil de mantener. También sabemos que Talend Studio es un «lienzo de formato libre» sobre el que «pintamos» nuestro código mediante una colorida y completa paleta de componentes, objetos de repositorio, metadatos y opciones de enlace. Entonces, ¿cómo podemos estar seguros de haber creado un diseño de tarea que aplique las mejores prácticas disponibles?

Patrones de diseño de tareas

Desde la versión 3.4, que es cuando yo empecé a usar Talend, los diseños de tarea siempre me han parecido muy importantes. Al principio no pensaba en patrones al desarrollar mis tareas; había usado Microsoft SSIS y otras herramientas parecidas antes, de modo que un editor visual como Talend no me resultaba ninguna novedad. En lugar de eso me centraba en funcionalidades básicas, la reusabilidad del código, después en el diseño del lienzo y, por último, en las convenciones de la nomenclatura. Hoy en día, después de haber desarrollado centenares de tareas de Talend con finalidades muy distintas, observo que mi código se ha vuelto más refinado, más reutilizable, más uniforme y, en efecto, han empezado a surgir patrones.

Después de incorporarme a Talend en enero de este año he tenido muchas oportunidades de repasar tareas creadas por nuestros clientes. Ha confirmado mi percepción de que cada desarrollador tiene siempre varias soluciones para cada caso práctico. Esto, me parece a mí, a muchos nos complica el problema. Los desarrolladores compartimos una forma de pensar, pero además pensamos que nuestra opción es la mejor o la única forma de desarrollar una tarea en particular. En el fondo somos conscientes de una idea que nos persigue, que nos susurra al oído, y es que es posible que exista otra forma mejor de hacer las cosas. Por ello buscamos o pedimos consejos para seguir mejores prácticas: en este caso, ¡los patrones de diseño de tareas!

Formular los elementos básicos

Cuando me planteo qué es necesario para lograr el mejor código para tareas posible, hay varios preceptos fundamentales que se cumplen siempre. Son el fruto de años de experiencia de cometer errores y de mejorar gracias a los éxitos. Representan importantes principios que forman unos cimientos sólidos sobre los que construir el código y deberían (en mi humilde opinión) tomarse en serio. A mi parecer son (sin ningún orden de importancia):

— Legibilidad: crear código que pueda descifrarse y entenderse fácilmente

— Susceptibilidad de escritura: crear código claro, sencillo, en el menor tiempo posible

— Facilidad de mantenimiento: crear la complejidad apropiada con un impacto mínimo por el cambio

— Funcionalidad: crear código que cumpla los requisitos

— Reusabilidad: crear objetos que se puedan compartir y unidades de trabajo atómicas

— Conformidad: crear una auténtica disciplina entre equipos, proyectos, repositorios y código

— Flexibilidad: crear código que sea maleable pero no se rompa

— Escalabilidad: crear módulos elásticos que ajusten el rendimiento a demanda

— Coherencia: crear siempre elementos en común

— Eficiencia: crear un flujo de datos y una utilización de componentes optimizados

— Compartimentación: crear módulos atómicos y especializados que cumplan una sola función

— Optimización: crear la máxima funcionalidad con la mínima cantidad de código

— Rendimiento: crear módulos eficaces que proporcionen el rendimiento más rápido

La clave consiste en lograr un equilibrio real entre estos preceptos; en concreto, los primeros tres, puesto que suelen estar en constante contradicción entre ellos. Es fácil cumplir dos, pero se acaba sacrificando el tercero. ¡Pruebe a ordenarlos todos por importancia si se atreve!

Son orientaciones, NO normas: ¡todo es cuestión de disciplina!

Antes de adentrarnos en los patrones de diseño de tareas, y en la línea de los preceptos básicos que acabo de ilustrar, vale la pena afianzar algunos conceptos más que deberían tenerse en cuenta. A menudo observamos que se aplican una serie de normas estrictas que no dejan margen para imprevistos que suelen dejarlas en entredicho. También veo, más de lo que me gustaría, el caso contrario: código inflexible, desordenado e incongruente procedente de distintos desarrolladores que a grandes rasgos están haciendo lo mismo; o peor aún, desarrolladores que propagan confusos batiburrillos de caos inconexo e improvisado. Sinceramente, a mí esto me parece una chapuza y una insensatez, porque tampoco cuesta tanto evitar esta situación.

Por este motivo, y otros bastante evidentes, yo prefiero empezar siempre elaborando y documentado unas «Orientaciones», no unas «Normas».

Es una forma de englobar los preceptos fundamentales y acompañarlos de instrucciones. Una vez elaborado el documento «Orientaciones para el desarrollo» y adoptado por todos los equipos que participen en el proceso de SDLC (Ciclo de vida de desarrollo de software, por sus siglas en inglés), los cimientos sustentan la estructura, la definición y el contexto. Inviertan en este aspecto y, a la larga, obtendrán resultados que serán del agrado de todos.

Les propongo un esquema que puede servirles de ayuda (cambien y amplíen a su gusto, que al final esto no es otra cosa que una orientación).

  1. Metodologías que deberían dar cuenta de CÓMO desea crear los distintos elementos
    1. Modelado de datos
      1. Holístico/Conceptual/Lógico/Físico
      2. Base de datos, NoSQL, EDW, archivos
    2. Controles de proceso SDLC
      1. Waterfall o Agile/Scrum
      2. Requisitos y especificaciones
    3. Resolución de errores y auditorías
    4. Gobernanza y administración de datos:
  2. Tecnologías que deberían enumerar HERRAMIENTAS (internas y externas) y cómo se relacionan entre sí
    1. SO y topología de infraestructuras
    2. Sistemas de gestión de bases de datos
    3. Sistemas NoSQL
    4. Cifrado y comprensión
    5. Integración de software de terceros
    6. Interfaces de servicios web
    7. Interfaces de sistemas externos
  3. Mejores prácticas que debería describir QUÉ directrices concretas deben seguirse y CUÁNDO
    1. Entornos (DEV/QA/UAT/PROD)
    2. Convenciones de nomenclatura
    3. Proyectos, tareas y joblets
    4. Objetos de repositorio
    5. Registro, seguimiento y notificaciones
    6. Códigos de devolución de tareas
    7. Rutinas de código (Java)
    8. Grupos de contexto y variables globales
    9. Bases de datos y conexiones NoSQL
    10. Datos fuente/destino y esquemas de archivos
    11. Puntos de entrada y salida de tarea
    12. Flujo de trabajo y diseño de tarea
    13. Utilización de componentes
    14. Paralelización
    15. Calidad de datos
    16. Tareas padre/hijo y joblets
    17. Protocolos de intercambio de datos
    18. Integración y despliegue continuos
      1. Control de código fuente integrado (SVN/GIT)
      2. Administración de ediciones y versionado
      3. Pruebas automatizadas
      4. Repositorio de artefactos y promoción
    19. Administración y operaciones
      1. Configuración
      2. Seguridad de usuario y autorizaciones
      3. Funciones y permisos
      4. Gestión de proyectos
      5. Tareas, programas y activadores
    20. Archivos y recuperación en caso de desastres

Algunos de los documentos adicionales que considero que deberían generarse y mantenerse son:

— Biblioteca de módulos: donde se describan todos los proyectos, métodos, objetos, joblets y grupos de contexto reutilizables

— Diccionario de datos: donde se describan todos los esquemas de datos y procedimientos almacenados relacionados

— Capa de acceso a datos: donde se describa todo lo relativo a la conexión y manipulación de datos

Ni que decir tiene que se tarda tiempo en crear toda esta documentación, pero su valor en todo el ciclo de vida supera con creces su coste. Mejor que sea sencillo, directo, al día (no hace falta que sea un manifiesto) y contribuirá notablemente al éxito de todos sus proyectos que lo utilicen reduciendo drásticamente los errores de desarrollo (que con el tiempo pueden llegar a salir aún más caros).

¿Podemos hablar ya de patrones de diseño de tareas?

¡Pues claro! Pero, antes, otra cosita. Yo soy de la opinión que todos los desarrolladores son capaces de adquirir tanto buenos hábitos como malos hábitos al escribir código. Es imprescindible ir cultivando los buenos. Empiece con algunos hábitos sencillos, como asignar siempre una etiqueta a cada componente. Esto hace que el código sea más legible y comprensible (uno de nuestros preceptos fundamentales). Una vez integrado este hábito, asegúrese de que todas las tareas estén concienzudamente organizadas en carpetas del repositorio, con nombres que tengan sentido y resulten adecuados a sus proyectos (en efecto: conformidad). Convenza a alguien más para que adopte el mismo estilo de registrar mensajes, a lo mejor utilizando un envoltorio de métodos habitual para la función System.out.PrintLn(), y fije un criterio de punto de entrada/salida compartido, con opciones de requisitos alternativos, para el código de la tarea (ambas medidas nos ayudan a cumplir varios preceptos a la vez). Con el tiempo, a medida que los equipos de desarrollo adoptan y utilizan unas disciplinas de directrices de desarrollo bien definidas, el código de los proyectos se vuelve más fácil de leer, de escribir y (mi favorito) de mantener por parte de cualquier integrante del equipo.

Patrones de diseño de tareas y mejores prácticas

Para mí los patrones de diseño de tareas de Talend nos obsequian con una propuesta de plantilla o unos diseños esquemáticos con elementos esenciales o necesarios pensados para un uso particular. Son patrones porque muchas veces pueden volver a usarse para crear tareas parecidas, con lo que todo el desarrollo del código se agiliza. Como es de prever, también existen patrones de uso habitual que pueden adoptarse en distintas casuísticas que, si se identifican y aplican correctamente, refuerzan toda la base del código, condensan esfuerzos y reducen la presencia de código repetitivo pero similar. Bien, pues empecemos por ahí.

He aquí siete mejores prácticas que vale la pena estudiar:

Flujo de trabajo y diseño del lienzo

Los componentes se pueden colocar y enlazar en el lienzo de la tarea de muchas maneras. Mi preferencia consiste en empezar a grandes rasgos «de arriba abajo», luego trabajar «hacia la izquierda y hacia la derecha», siendo normalmente los flujos hacia la izquierda una ruta de errores y hacia la derecha o descendente la ruta normal o deseada. Evitar líneas de enlace que se entrecrucen siempre que sea posible es conveniente y, desde la versión 6.0.1, las líneas de enlace perfectamente curvadas también siguen esta estrategia.

Personalmente, me incomoda el patrón «en zigzag», que es cuando los componentes se sitúan «de izquierda a derecha» en serie y luego, al llegar al extremo de la derecha, el próximo componente baja y regresa al extremo izquierdo para seguir de esta guisa; a mí este patrón me parece una torpeza y puede resultar difícil de mantener, pero entiendo que es fácil de escribir. Utilice este patrón si no hay más remedio, pero podría indicar la posibilidad de que la tarea está haciendo más de lo que debería o que quizá no esté organizada correctamente.

Módulos de tareas atómicos - Tareas padre/hijo

Las tareas grandes con muchos componentes, las cosas como son, cuestan de entender y de mantener. Conviene evitarlo descomponiéndolas en tareas más pequeñas, o en unidades de trabajo, siempre que se pueda. Luego ejecútelas como tareas hijo de una tarea padre (con el componente tRunJob), cuya finalidad incluye su control y ejecución. Esto también genera la oportunidad de manejar mejor los errores y lo que suceda a continuación. Recuerde que una tarea abarrotada puede ser difícil de entender, depurar/reparar y prácticamente imposible de mantener. Las tareas más pequeñas y sencillas con una finalidad clara destacan en el lienzo por su propósito, son casi siempre fáciles de depurar/reparar y por su mantenimiento, que comparado con las otras es pan comido.

Es perfectamente aceptable crear jerarquías de tareas padre/hijo anidadas, pero hay una serie de limitaciones prácticas que debe tener en cuenta. Según el uso de memoria de la tarea, los parámetros enviados, las preocupaciones en materia de pruebas/depuraciones y las técnicas de paralelización (descritas más abajo), un buen patrón de diseño de tareas no debería superar los tres niveles anidados de invocaciones padre/hijo de tRunJob. Puede resultar más seguro ir más allá, pero me parece que está justificado limitarlo a 5 niveles en prácticamente todos los casos.

Entre tRunJob y joblets

La fina línea que separa decidir entre una tarea hijo o un joblet es que la tarea hijo se la «invoca» desde la tarea y el joblet está «incluido» en su tarea. Ambas nos dan la oportunidad de crear módulos de código reutilizable o genérico. Una estrategia muy eficaz en cualquier patrón de diseño de tareas consistiría en incorporar su uso de la forma adecuada.

Puntos de entrada y salida

Todas las tareas de Talend tienen que empezar y terminar en algún sitio. Talend proporciona dos componentes básicos: tPreJob y tPostJob, cuya función consiste en ayudar a controlar qué sucede antes y después de que se ejecute el contenido de una tarea. Yo me los imagino como los pasos de «Initialize» (Inicializar) y «WrapUp» (Concluir) de mi código. Se comportan como cabría esperar: tPreJob se ejecuta primero, a continuación se ejecuta el código en sí y, por último, el código tPostJob. Cabe mencionar que el código tPostJob se ejecutará independientemente de que tope con una salida diseñada en el cuerpo del código (como un componente tDie o una opción de casilla de verificación del componente de «die on error» (detener cuando se produzca un error).

En materia de puntos de entrada y salida de tareas, también debería sopesarse el uso de los componentes tWarn y tDie. Son componentes que aportan control programable sobre el lugar y la manera en la que debe realizarse una tarea. También permite mejorar la gestión de errores, el registro y las oportunidades de recuperación.

Una cosa que me gusta hacer con este patrón de diseño de tareas es utilizar tPreJob para inicializar variables de contexto, establecer conexiones y registrar información importante. Para tPostJob: cerrar conexiones y otras funciones de limpieza importantes, y más registro. Es bastante sencillo, ¿a que sí? ¿Es así cómo suele trabajar?

Resolución y registro de errores

Esto es muy importante, incluso crítico, y si crea un patrón de diseño de tarea común de la forma adecuada, puede establecer un mecanismo fácilmente reutilizable para prácticamente todos sus proyectos. Mi patrón de tarea pasa por crear un joblet «logPROCESSING»’ para tener un procesador de registro uniforme y mantenible que pueda incluirse en cualquier tarea, ADEMÁS de incorporar «códigos de retorno» bien definidos para ofrecer conformidad, reusabilidad y una gran eficiencia. Por no decir que es fácil de escribir, de leer e, insisto, bastante fácil de mantener. Creo que, una vez haya desarrollado «su propia forma» de gestionar y registrar los errores en todas las tareas de su proyecto, se le va a dibujar una sonrisa en la cara de oreja a oreja. ¡Adaptar y adoptar!

Las versiones más recientes de Talend traen incorporada la compatibilidad con el uso de Log4j y un servidor de registro. Basta con habilitar la opción del menú Project Settings (Ajustes del proyecto) >Log4j y configurar el servidor Log Stash en el TAC. Definitivamente, incorporar esta funcionalidad básica a sus tareas cuenta como mejor práctica.

Vínculos entre componentes: OnSubJobOK/ERROR frente a OnComponentOK/ERROR (y Run If)

A cualquier desarrollador de Talend a veces puede resultarles un poco confusas las diferencias que existen entre los vínculos «On SubJob» o «On Component». La parte de «OK» o «ERROR» es obvia. Así pues, ¿cuáles son estas diferencias de «conexiones tipo activador» y cómo afectan los flujos de diseño de tareas?

Las «conexiones activadoras’» entre componentes definen la secuencia de procesamiento y el flujo de datos donde se produzcan dependencias entre componentes dentro de una subtarea. Las subtareas se caracterizan porque un componente tiene uno o más componentes vinculados que corresponden al flujo de datos actual. Puede haber distintas subtareas en una misma tarea y por defecto esto se visualiza con una casilla resaltada en azul (que se puede activar o desactivar desde la barra de herramientas) alrededor de todos los componentes relacionados de la subtarea.

Un activador «On Subjob OK/ERROR» continuará el proceso hasta la próxima subtarea «vinculada» una vez todos los componentes de la subtarea hayan terminado de procesarse. Esto debería utilizarse tan solo desde el componente de inicio de la subtarea. Un activador «On Component OK/ERROR» continuará el proceso hasta el próximo componente «vinculado» una vez aquel componente concreto haya terminado de procesarse. Un activador del tipo «Run If» puede ser de gran utilidad cuando la continuación del proceso al próximo componente «vinculado» se base en una expresión programable de Java.

¿En qué consiste un bucle de tareas?

En prácticamente todos los patrones de diseño de tareas son de gran importancia el «Main Loop» (Bucle principal) y los «Secondary Loops» (Bucles secundarios) del código. Se trata de puntos en los que se ejerce el control de la salida potencial de la ejecución de una tarea. El «bucle principal» suele representarse con el procesamiento superior de un conjunto de resultados de un flujo de datos que, una vez terminado, finaliza la tarea. Los «bucles secundarios» se anidan en un bucle de orden superior y suelen exigir un control considerable para garantizar la salida correcta de una tarea. Yo siempre identifico el «bucle principal» y me aseguro de añadir un componente tWarn y un tDie al componente controlador. Normalmente tDie está configurado para salir de la máquina virtual de Java (JVM) inmediatamente (aunque conviene observar que incluso entonces se ejecutará el código tPostJob). Estos puntos de salida de nivel superior emplean un simple «0» para el código de retorno correcto y un «1» para el incorrecto, pero es mejor seguir la orientación sobre «códigos de retorno» que hemos establecido antes. Los «bucles secundarios» (y otros componentes críticos del flujo) son lugares idóneos para incorporar más componentes tWarn y tDie (en los que tDie NO esté configurado para salir de la JVM de inmediato).

A continuación se ilustran gran parte de las mejores prácticas en materia de patrones de diseño de tareas que hemos analizado. Fíjese que, si bien he usado etiquetas de componentes que son útiles, me he saltado un poco las normas en cuanto a la posición de los componentes. Igualmente, el resultado es una tarea muy legible y mantenible que ha resultado bastante fácil de escribir.

Conclusión

En realidad, no puedo decir que con este texto haya resuelto todas sus dudas sobre patrones de diseño de tareas; seguramente no ha sido así. ¡Pero por algo se empieza! Hemos abordado algunos conceptos básicos y hemos apuntado una manera de proceder y una filosofía. Espero que le sirva y que le anime a plantearse aspectos cruciales para usted, amable lector.

Es evidente que voy a tener que redactar otra entrada del blog (o algunas más) sobre este tema para no dejar ningún fleco. La próxima entrega se centrará en algunos valiosos aspectos más avanzados y distintos casos prácticos con los que podemos encontrarnos todos con pequeñas variaciones. Además el equipo de Arquitectura de Éxito de los Clientes está trabajando en código Talend de ejemplo para complementar todos estos casos prácticos. Estará disponible en el Centro de Asistencia de Talend para los clientes inscritos dentro de poco. ¡Esté atento!

Recursos relacionados

5 maneras de convertirse en un héroe de la integración de datos

Productos mencionados

Open Studio for Data Integration

Join The Conversation

0 Comments

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *