Tutorial - parte 2/2

Asegúrese de completar la primera parte del tutorial primero. Algunas ideas explicadas anteriormente no se repiten aquí.

Invocar otros sistemas

Para resumirlo, en la parte anterior configuramos un clúster de Zato completamente funcional, implementamos un servicio y creamos un canal de API REST a través del cual se invocaba el servicio.

En esta parte haremos que el servicio realmente obtenga datos de sistemas remotos y los procese de acuerdo con sus requisitos comerciales. Los capítulos iniciales de esta parte del tutorial usarán principalmente el Panel de Control para configurar conexiones externas y luego regresaremos a Python.

Para invocar otros sistemas, aplicaciones y API, los servicios de Zato utilizan conexiones salientes, que es el concepto que cubriremos ahora.

Conexiones salientes

Las conexiones salientes son la contraparte natural de los canales. Mientras que los canales permiten hacer que los servicios de Zato estén disponibles para clientes API externos, con conexiones salientes (outconns para abreviar) son los servicios de Zato los que consultan sistemas externos.

Los outconns se invocan típicamente usando atributos de self.out, por ejemplo self.out.rest, self.out.amqp, self.out.sap y así sucesivamente, manteniendo un grupo de conexiones internamente cuando sea necesario para que los servicios puedan concentrarse solo en la parte de invocación.

Las conexiones salientes, como canales u otros elementos de Zato, le permiten aislar la lógica empresarial de los servicios de su configuración. Como veremos más adelante, un servicio solo se refiere a nombres abstractos como "CRM" cuando quiere acceder a algún recurso externo, sin necesidad de que sepa dónde está realmente el CRM, bajo qué dirección y protegido con qué credenciales.

Separar la lógica de la configuración implementar el mismo código sin cambios en múltiples entornos. También significa que es fácil migrar a entornos nuevos o modificados. Por ejemplo, si hoy su servicio se conecta a una API REST con una definición de seguridad de autenticación básica, pero mañana la API estará protegida con claves privadas TLS, será solo cuestión de una actualización de la configuración y el servicio seguirá funcionando sin interrupciones, sin ningún tiempo de inactividad.

Hablando de configuración, a lo largo del tutorial usamos principalmente el Panel de Control para administrar la configuración, pero se puede exportar a YAML o JSON e importar en otros entornos también, lo contaremos más adelante.

Además de los outconns, también es posible instalar bibliotecas desde PyPI e invocar sistemas remotos usando bibliotecas cliente para tipos de conexión distintos a los que Zato tiene incorporados.

Para los propósitos del tutorial, todos los puntos de conexión REST y AMQP ya están preparados para que no necesite configurar nada y podemos comenzar creando los outconns ahora.

Conexiones salientes REST

En el Panel de Control en http://localhost:8183, vaya a Connections -> Outgoing -> REST y haga clic en Create a new REST outgoing connection.

Aparecerá un formulario, complételo según la tabla a continuación. Tenga en cuenta que el método predeterminado de ping es HEAD, lo usaremos en la siguiente sección.

OpciónValor
NameCRM
Hosthttp://tutorial.zato.io:9193
URL path/get-user
Data formatJSON
SecurityNo security

También necesitamos una conexión REST saliente al sistema de Pagos (Payments), como en la siguiente tabla.

OpciónValor
NamePayments
Hosthttp://tutorial.zato.io:9193
URL path/balance/get
Data formatJSON
SecurityNo security

Hacer ping a las conexiones salientes de REST

Una vez que hemos creado las salidas REST, podemos verificar si tienen conectividad con los sistemas a los que apuntan haciéndoles ping: hay un enlace de Ping para cada salida.

Haga clic en él y confirme que la respuesta es similar a la siguiente; siempre que esté en verde, la conexión funciona bien.

La conexión no hace ping desde su host local sino desde uno de los servidores en su clúster; de esta manera, puede confirmar que realmente son sus servidores, en lugar de su sistema local, los que tienen acceso a un punto final remoto.

Conexiones salientes AMQP

Las conexiones AMQP se crean de manera similar a las REST, excepto que en lugar de ir directamente a las conexiones salientes, primero visitamos Connections -> Definitions -> AMQP en el Panel de Control.

Las definiciones de conexión son objetos de configuración reutilizables que se emplean si una determinada tecnología o protocolo se puede usar en ambos canales y conexiones salientes, como es el caso de AMQP. El tutorial solo envía mensajes a AMQP, pero en otro proyecto puede tener tanto los canales AMQP como los outconns que apuntan al mismo agente y una definición incluye la configuración de ambos.

Tener una única definición con credenciales para ambos tipos hace que sea conveniente actualizar las partes comunes de la configuración en un solo lugar, por ejemplo, después de cambiar un nombre de usuario o host en una definición de AMQP, todos los canales y conexiones salientes que utilicen esta definición se reconfigurarán automáticamente y se volverán a conectar según sea necesario.

Por lo tanto, vaya a Connections -> Definitions -> AMQP y cree una nueva definición como en esta tabla.

OpciónValor
NameFraud Detection Definition
Hosttutorial.zato.io
Port25701
Virtual host/
Usernameguest

Esto creó una nueva definición de conexión AMQP y necesitamos establecer la contraseña del usuario. Haga clic en Change password e ingrese guest; tenga en cuenta que la contraseña cambia periódicamente y puede ser diferente si visita el tutorial en otro momento.

En este punto, tenemos una definición, pero por sí solo, no intercambiará mensajes con un bróker AMQP, solo los canales o outconns pueden hacerlo utilizando la configuración y las credenciales de sus definiciones principales.

Por lo tanto, cree una nueva salida AMQP en Connections -> Outgoing -> AMQP. A excepción de los dos valores específicos a continuación, puede dejar el resto sin cambios con valores en blanco o predeterminados.

OpciónValor
NameFraud Detection Connection
DefinitionFraud Detection Definition

Probando la conexión a AMQP con publicaciones

Podemos hacer verificar la conexión a un agente AMQP remoto al publicar un mensaje en él. Vaya a Connections -> Outgoing -> AMQP y haga clic en Publish en la tabla que se mostrará.

Ahora, envíe un mensaje de prueba utilizando los siguientes datos.

OpciónValor
Data(Cualquiera)
Exchangetutorial
Routing keyapi

Habrá una respuesta sobre un fondo verde confirmando que el mensaje se publicó correctamente.

Todas las conexiones salientes se han creado, ahora podamos volver al servicio Python.

Volver al servicio

La lógica de nuestro servicio será:

  • Aceptar un parámetro user_name en la entrada
  • Consultar el CRM para obtener datos básicos del usuario, incluidos número de cuenta y tipo de usuario
  • Consultar Payments para obtener detalles de la cuenta por número de cuenta
  • Si user_type coincide con la configuración, notifique al sistema de detección de fraudes

En un proyecto de integración más grande, un usuario individual tendría más de una cuenta bancaria, pero aquí lo mantenemos simple y asumimos que cada usuario tiene una sola cuenta bancaria.

Aquí está la implementación completa de la lógica anterior en Python.

# -*- coding: utf-8 -*-
# zato: ide-deploy=True

# Zato
from zato.server.service import Service

# ##############################################################################

class GetUserDetails(Service):
    """ Devuelve detalles de un usuario a partir del ID de la persona.
    """
    name = 'api.user.get-details'

    def handle(self):

        # Para uso posterior
        user_name = self.request.payload['user_name']

        # Obtener datos del CRM ..
        crm_data = self.invoke_crm(user_name)

        # .. extraer la información deseada del CRM ..
        user_type = crm_data['UserType']
        account_no = crm_data['AccountNumber']

        # .. obtener datos desde Payments ..
        payments_data = self.invoke_payments(user_name, account_no)

        # .. extraer la información deseada del CRM ..
        account_balance = payments_data['ACC_BALANCE']

        # .. opcionalmente, notificar al sistema de detección de fraudes ..
        if self.should_notify_fraud_detection(user_type):
            self.notify_fraud_detection(user_name, account_no)

        # .. ahora, se genera la respuesta a devolver.
        self.response.payload = {
          'user_name': user_name,
          'user_type': user_type,
          'account_no': account_no,
          'account_balance': account_balance,
      }

# ##############################################################################

    def invoke_crm(self, user_name):

        # Registrar lo que estamos a punto de hacer
        self.logger.info('Invoking CRM; u=%s', user_name)

        # Obtener una conexión al CRM ..
        conn = self.out.rest['CRM'].conn

        # .. crear una solicitud para el CRM ..
        request = {
            'UserName': user_name,
        }

        # .. consultar el CRM ..
        crm_response = conn.get(self.cid, request)

        # .. devolver datos recibidos desde el CRM.
        return crm_response.data

# ##############################################################################

    def invoke_payments(self, user_name, account_no):

        # Registrar lo que estamos a punto de hacer
        self.logger.info('Invoking Payments; u=%s, a=%s', user_name, account_no)

        # Obtener una conexión a Payments ..
        conn = self.out.rest['Payments'].conn

        # .. crear una solicitud para Payments ..
        request = {
            'ACC_NUM': account_no,
        }

        # .. preparar parámetros de la consulta ..
        params = {'USER': user_name}

        # .. consultar Payments ..
        response = conn.post(self.cid, params=params)

        # .. devolver datos recibidos desde Payments.
        return response.data

# ##############################################################################

    def notify_fraud_detection(self, user_name, account_no):

        # Registrar lo que estamos a punto de hacer
        self.logger.info('Notifying Fraud detection; u=%s', user_name)

        # Datos para enviar al sistema de detección de fraudes
        data = 'User `{}` accessed `{}`'.format(user_name, account_no)

        # Configuración de AMQP
        outconn = 'Fraud Detection Connection'
        exchange = '/tutorial'
        routing_key = 'api'

        # Enviar el mensaje al sistema de detección de fraudes
        self.out.amqp.send(data, outconn, exchange, routing_key)

# ##############################################################################

    def should_notify_fraud_detection(self, user_type):
        config = self.server.user_config.get('tutorial')
        if config:
            return config.notify_fraud.get(user_type)

# ##############################################################################

Inmediatamente podemos observar que:

  • Este es un código de alto nivel: en cuanto a la implementación, operamos al nivel de los diccionarios de Python y algunos métodos

  • El servicio es, en gran parte, independiente de los sistemas subyacentes que necesita integrar; no se preocupa por los detalles de ninguno de los protocolos que se utilizan en este esfuerzo de integración

  • Usamos métodos simples, como .get, .post o .send para conectarnos a sistemas externos y es el trabajo de la plataforma Zato traducirlo en invocaciones reales, por ejemplo el servicio solo envía datos a 'CRM', 'Payments' o 'Fraud Detection Connection', pero no necesita saber qué punto final REST o corredor AMQP se refiere específicamente

  • Verificamos si enviamos notificaciones al sistema de detección de fraudes usando self.server.user_config; esto hace referencia a un archivo local config que crearemos en un momento. Pero, ya que este archivo aún no existe, el método should_notify_fraud_detection chequea si puede usarlo.

Invocar el servicio

Usemos curl para acceder al servicio:

# Asegúrese de usar aquí la contraseña establecida en la primera parte del tutorial
$ curl -XPOST http://api:<password>@localhost:11223/api/v1/user \
      -d '{"user_name": "my.user"}'
# Esta es nuestra respuesta
{"user_name":"my.user","user_type":"RGV","account_no":"123456","account_balance":"357.9"}
$

En los registros del servidor (que se encuentran en ~/env/qs-1/server1/logs/server.log):

INFO - Invoking CRM; u=my.user
INFO - Invoking Payments; u=my.user, a=123456

Ya casi hemos terminado; la única parte que queda es garantizar que el sistema de detección de fraudes sea notificado cuando sea necesario. Para eso, necesitamos discutir nuestras opciones de configuración en tiempo de ejecución.

Configuración en tiempo de ejecución

Cada proyecto debe mantener su configuración de tiempo de ejecución en alguna ubicación. Lo que se almacena en dicha configuración son detalles específicos del proyecto, como reglas o condiciones comerciales.

En términos generales, hay dos tipos de ubicaciones donde se puede guardar. Ambos tipos son una buena opción y, sobre todo, es decisión del arquitecto qué utilizar.

  • Archivos de configuración
  • Bases de datos, como Redis, MongoDB, Vault, SQL o similares

Vale la pena decir que en Zato los archivos de configuración se recargan automáticamente y se almacenan en caché en la RAM cada vez que se actualizan. Esto significa que puede cambiar su contenido sin reiniciar el servidor y sin necesidad de volver a implementar sus servicios.

Para mantenerlo simple, así no requiere la instalación de componentes adicionales como Redis, vamos a utilizar un archivo config.

Guarde las líneas siguientes en `~/env/qs-1/server1/config/repo/user-conf/tutorial.conf:

[notify_fraud]
RGV=True

Tal archivo será recogido por Zato y sus contenidos estarán disponibles para un servicio a través de self.server.user_config, por ejemplo, para acceder al valor de RGV, puede usar self.server.user_config.tutorial.notify_fraud.RGV.

Hay varias ventajas significativas para usar archivos config:

  • La claridad de la intención. Es fácil leer un archivo .ini.

  • El rendimiento. Los contenidos de tales archivos son sincronizados automáticamente a la RAM cada vez que un archivo cambia en el disco, lo que quiere decir que no hay búsquedas realizadas en el tiempo de ejecución en una base de datos remota y no hay lectura de disco.

  • Cada archivo es convertido a una estructura de diccionario que, al mismo tiempo, puede ser accedida por medio de la sintaxis de punto. Por ejemplo, todos los métodos del diccionario continuarán funcionando y puede comprobar si existen claves en un archivo.

  • La capacidad de guardar configuración en un repositorio de código. Simplemente realice un push a un archivo al repositorio de git y luego realice un pull al directorio `user-conf`` del servidor.

  • Los archivos config son archivos regulares de texto, lo que significa que pueden ser generados por medio de otras herramientas. Por ejemplo, si usted usa una herramiento UML para su modelaje, siempre es posible generar tal archivo config desde el formato nativo dado de la herramienta.

Si ahora invocamos el servicio nuevamente, notaremos una nueva entrada en los registros del servidor porque esta vez también estamos enviando un mensaje AMQP (vea la línea resaltada):

  INFO - Invoking CRM; u=my.user
  INFO - Invoking Payments; u=my.user, a=123456
  INFO - Notifying Fraud detection; u=my.user

En cuanto a la implementación, hemos terminado, esto es todo. Lo que tenemos ahora es una plataforma de integración completamente funcional y un servicio que integra clientes API con tres sistemas backend.

Más características

Solo hemos cubierto la punta del iceberg en términos de ofertas de Zato, así que enumeremos rápidamente todas las diversas características que están disponibles para construir API y sistemas del lado del servidor.

  • Agente de mensajes con publicación/suscripción a temas y colas de mensajes
  • Transferencia de archivos
  • Programador de tareas
  • Inicio de sesión único de REST y Python
  • Estadísticas
  • Canales: AMQP, HL7, IBM MQ, JSON-RPC, REST, SOAP, WebSockets y ZeroMQ
  • Conexiones salientes: AMQP, FTP, HL7, IBM MQ, LDAP, Redis, MongoDB, Odoo, REST, SAP RFC, SFTP, SOAP, SQL, WebSockets y ZeroMQ
  • Más tipos de conexión: AWS S3, Dropbox, Built-in cache, Memcached, ElasticSearch, Solr, Swift, Cassandra, Slack, Telegram, IMAP, SMTP y Twilio
  • Seguridad: API keys, HTTP Basic Auth, JWT, NTLM, OAuth, RBAC, SSL/TLS, Vault, WS-Security y XPath
  • CLI y API ofrecidos como servicios (por ejemplo el Panel de Control en sí mismo es un cliente API de los servicios públicos propios de la plataforma)
  • Numerosas herramientas y utilidades más pequeñas

También echemos un vistazo rápido a dos características en particular: automatización de implementación y pruebas de API.

Automatización DevOps

A lo largo del tutorial, solo hemos estado usando el Panel de Control para configurar objetos en Zato, como canales o conexiones salientes. Esto es bueno, pero cuando se trata de implementación automatizada, nos gustaría tener una forma de exportar nuestra configuración e importarla en otro entorno. Esto es exactamente lo que haremos ahora.

Entre otras opciones de la línea de comandos está zato enmasse; este comando le permite exportar definiciones de objetos en Zato, fusionar varios e importarlos en otros entornos. El formato de datos que utiliza puede ser YAML o JSON, según las preferencias de cada uno.

El flujo de trabajo típico con enmasse es seguir exportando progresivamente definiciones desde un entorno de desarrollo y almacenarlas en un repositorio de código, exportándolas a otros entornos cuando llegue el momento. Debido a que son archivos simples legibles por humanos, es fácil diferenciarlos o reemplazar su contenido durante la implementación.

En la práctica, enmasse se usa así:

$ zato enmasse ~/env/qs-1/server1 --export-odb
ODB objects read
ODB objects merged in
Data exported to /opt/zato/zato-export-2021-11-29T15_27_35_609729.yml
$

El archivo resultante contendrá definiciones de todos los objetos que se encuentran en la base de datos del clúster. Por ejemplo, podemos encontrar nuestra conexión de salida AMQP entre ellos:

..

outconn_amqp:

  - def_name: 'Fraud Detection Definition'
    name: 'Fraud Detection Connection'
    priority: 5

..

Después del preprocesamiento, dicho archivo se puede almacenar en git. Además, para reducir cualquier posibilidad de conflictos, varios desarrolladores pueden tener cada uno su propio archivo con la configuración y enmasse los combinará cuando sea el momento de importar los objetos en su totalidad.

En cuanto a cómo funciona la importación: es similar a la llamada anterior. Tenga en cuenta el uso de la bandera `--replace-odb-objects: esto le dice a enmasse que actualice los objetos ya existentes en el lugar, de lo contrario, se negaría a continuar por si no fuera su intención reemplazar lo que ya está almacenado en la base de datos operativa del clúster (ODB).

$ zato enmasse ~/env/qs-1/server1 --import \
  --input ./zato-export-2021-11-29T15_27_35_609729.yml \
  --replace-odb-objects
Invoking zato.outgoing.amqp.edit for outconn_amqp
Updated object `Fraud Detection Connection`
$

Prueba de API

Tenemos implementación, configuración e implementación automatizada, por lo que ahora podemos agregar pruebas de API.

Zato tiene una herramienta de línea de comandos llamada apitest que te permite escribir pruebas en inglés puro y podemos usarla para probar el proceso de integración desarrollado en este tutorial:

Copiemos la prueba siguiente para que sea más fácil de analizar:

Feature: Zato Tutorial

Scenario: *** Call api.user.get-details ***

    Given address "http://localhost:11223"
    Given Basic Auth "api" "<password>"
    Given URL path "/api/v1/user"
    Given format "JSON"
    Given HTTP method "GET"
    Given request is "{}"
    Given path "/user_name" in request is "my.user"

    When the URL is invoked

    Then path "/user_name" is "my.user"
    And path "/user_type" is "RGV"
    And path "/account_no" is "123456"
    And path "/account_balance" is "357.9"
    And status is "200"
    And header "X-Zato-CID" is not empty
  • Todas las pruebas de API están escritas en inglés, sin necesidad de programación; esto hace que sea fácil para todos participar en su creación.

  • Las pruebas se dividen en funciones y escenarios. Por ejemplo una característica puede ser de "Administración de cuentas de usuario" con escenarios individuales que prueban CRUD y otras partes de la funcionalidad.

  • Los escenarios pueden transmitir información y contexto a través de pruebas, por ejemplo, es posible tener un escenario de configuración que prepare los datos para probar, seguido de pruebas y finalice con una fase de desmontaje que limpie los datos de prueba. Esto es como con las pruebas unitarias, excepto que aquí probamos API completas utilizando entornos que están en funcionamiento.

  • Es posible formar una cadena de invocaciones de pruebas, por ejemplo, una prueba puede obtener datos del usuario, extraer lo que se necesita y pasarlo a otra prueba que necesita esta información

  • La configuración de las pruebas se puede guardar en archivos externos o variables de entorno, lo que significa que las mismas pruebas se pueden usar con diferentes entradas, salidas o en diferentes entornos.

  • Debido a que apitest se ejecuta desde la línea de comandos, es fácil conectarlo a cualquier tipo de canalización de desarrollo e implementación.

Con esto concluye el tutorial; ahora está listo para explorar la plataforma y crear sus propias soluciones API usando Zato.

Schedule a meaningful demo

Book a demo with an expert who will help you build meaningful systems that match your ambitions

"For me, Zato Source is the only technology partner to help with operational improvements."

John Adams, Program Manager of Channel Enablement at Keysight