Tutoriel - partie 2/2

Veillez à compléter d'abord la partie 1 du tutoriel. Certaines idées qui y ont été expliquées précédemment ne sont pas reprises ici.

Appel d'autres systèmes

Pour résumer, dans la partie précédente, nous avons mis en place un cluster Zato fonctionnel, déployé un service et créé un canal API REST par lequel le service était invoqué.

Dans cette partie, nous ferons en sorte que le service obtienne réellement des données de systèmes distants et les traite conformément à ses exigences commerciales. Les premiers chapitres de cette partie du tutoriel utiliseront principalement le Dashboard pour configurer les connexions externes et nous reviendrons à Python par la suite.

Pour invoquer d'autres systèmes, applications et API, les services Zato utilisent des connexions sortantes, ce qui est le concept que nous allons aborder maintenant.

Connexions sortantes

Les connexions sortantes sont la contrepartie naturelle des canaux. Alors que les canaux permettent de mettre les services Zato à la disposition des clients API externes, avec les connexions sortantes (outconns en abrégé), ce sont les services Zato qui invoquent les points de terminaison des systèmes externes.

Les outconns sont généralement invoqués en utilisant les attributs de self.out, par exemple self.out.rest, self.out.amqp, self.out.sap et ainsi de suite, en maintenant un pool de connexion en interne lorsque cela est nécessaire afin que les services puissent se concentrer sur la partie invocation.

Les connexions sortantes, comme les canaux ou d'autres éléments Zato, vous permettent d'isoler la logique des services de leur configuration. Comme nous le verrons plus tard, un service ne se réfère à des noms abstraits comme "CRM" que lorsqu'il veut accéder à une ressource externe, sans qu'il ait besoin de savoir où se trouve réellement le CRM, sous quelle adresse et avec quelles informations d'identification.

Séparer la logique de la configuration vous permet de déployer le même code inchangé dans plusieurs environnements. Cela signifie également qu'il est facile de migrer vers des environnements nouveaux ou modifiés. Par exemple, si aujourd'hui votre service se connecte à une API REST avec une définition de sécurité Basic Auth mais que demain l'API sera sécurisée avec des clés privées TLS, il suffira d'une mise à jour de la configuration et le service continuera à fonctionner sans interruption, sans aucun temps mort.

En parlant de configuration, tout au long du tutoriel, nous utilisons principalement le Dashboard pour gérer la configuration mais elle peut être exportée vers YAML ou JSON et importée dans d'autres environnements également, nous en parlerons plus tard.

En plus des outconns, il est également possible d'installer des bibliothèques à partir de PyPI et d'invoquer des systèmes distants en utilisant des bibliothèques clientes pour des types de connexion autres que ceux intégrés dans Zato à l'aide de bibliothèques clientes pour des types de connexion autres que ceux que Zato a intégrés.

Pour les besoins du tutoriel, tous les points de terminaison REST et AMQP sont déjà préparés pour vous de sorte que vous n'avez pas besoin de configurer quoi que ce soit et nous pouvons maintenant commencer par créer les outconns.

Connexions sortantes REST

Dans le tableau de bord, à l'adresse http://localhost:8183, allez dans "Connexions -> Outgoing -> REST" et cliquez sur "Créer une nouvelle connexion sortante REST".

Un formulaire va s'afficher, remplissez-le selon le tableau ci-dessous. Notez la méthode de ping HEAD par défaut, nous allons l'utiliser dans la section suivante.

En-têteValeur
NameCRM
Hosthttp://tutorial.zato.io:9193
URL path/get-user
Data formatJSON
SecurityNo security

Nous avons également besoin d'une connexion REST sortante vers le système de paiement, comme dans le tableau suivant.

En-têteValeur
NamePayments
Hosthttp://tutorial.zato.io:9193
URL path/balance/get
Data formatJSON
SecurityNo security

Pinguer des connexions sortantes REST

Après avoir créé les outconns REST, nous pouvons vérifier s'ils ont une connectivité aux systèmes vers lesquels ils pointent en les pinguant - il y a un lien Ping pour chaque outconn.

Cliquez dessus et confirmez que la réponse est similaire à celle ci-dessous - tant qu'elle est en vert, la connexion fonctionne bien.

La connexion est envoyée non pas à partir de votre hôte local, mais à partir de l'un des serveurs de votre cluster. De cette façon, vous pouvez confirmer que ce sont bien vos serveurs, et non votre système local, qui ont accès à un point d'extrémité distant.

Connexions sortantes AMQP

Les connexions AMQP sont créées de la même manière que les connexions REST, sauf qu'au lieu d'aller directement aux connexions sortantes, nous visitons d'abord "Connexions -> Définitions -> AMQP" dans le Dashboard.

Les définitions de connexion sont des objets de configuration réutilisables qui sont employés si une technologie ou un protocole donné peut être utilisé dans les canaux et les connexions sortantes, ce qui est le cas avec AMQP. Le tutoriel n'envoie des messages qu'à AMQP mais dans un autre projet, vous pouvez avoir à la fois des canaux AMQP et des outconns pointant vers le même broker et une définition englobant la configuration pour les deux.

Le fait de disposer d'une définition unique avec des informations d'identification pour les deux types de services permet de mettre à jour les parties communes de la configuration à un seul endroit. Par exemple, si vous modifiez un nom d'utilisateur ou un hôte dans une définition AMQP, tous les canaux et toutes les connexions sortantes utilisant cette définition se reconfigureront automatiquement et se reconnecteront si nécessaire.

Ainsi, allez dans Connexions -> Définitions -> AMQP et créez une nouvelle définition comme dans ce tableau.

En-têteValeur
NameFraud Detection Definition
Hosttutorial.zato.io
Port25701
Virtual host/
Usernameguest

Cela a créé une nouvelle définition de connexion AMQP et nous devons définir le mot de passe de l'utilisateur. Cliquez sur "Changer le mot de passe" et entrez guest - notez que le mot de passe change périodiquement et qu'il peut être différent si vous visitez le tutoriel dans quelque temps.

A ce stade, nous avons une définition mais en elle-même, elle ne va pas échanger de messages avec un broker AMQP, ce sont seulement les canaux ou les outconns qui le font en utilisant la configuration et les credentials de leurs définitions parentes.

Par conséquent, créez un nouveau outconn AMQP à Connections -> Outgoing -> AMQP. A l'exception des deux valeurs spécifiques ci-dessous, vous pouvez laisser le reste inchangé avec des valeurs vides ou par défaut.

En-têteValeur
NameFraud Detection Connection
DefinitionFraud Detection Definition

Pinguer AMQP avec publications

Nous pouvons envoyer un ping à un broker AMQP distant en lui publiant un message. Allez dans "Connections -> Outgoing -> AMQP" et cliquez sur "Publier" dans le tableau qui s'affiche.

Maintenant, envoyez un message de test en utilisant la configuration ci-dessous.

En-têteValeur
Data(Any)
Exchangetutorial
Routing keyapi

Il y aura une réponse sur fond vert confirmant que le message a été publié avec succès.

Toutes les connexions sortantes sont créées et nous pouvons maintenant reporter notre attention sur le service Python.

Retour au service

La logique de notre service sera :

  • Accepter le user_name en entrée
  • Invoquer le CRM pour obtenir les données de base des utilisateurs, y compris account_number et user_type
  • Invoquer Payments pour obtenir les détails du compte par account_number
  • Si user_type correspond à la configuration, notifier Fraud detection

Dans un projet d'intégration plus important, un utilisateur individuel pourrait avoir plus d'un compte bancaire, mais ici nous restons simples et supposons que chaque utilisateur a un compte bancaire.

Voici l'implémentation complète en Python de la logique ci-dessus.

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

# Zato
from zato.server.service import Service

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

class GetUserDetails(Service):
    """ Returns details of a user by the person's ID.
    """
    name = 'api.user.get-details'

    def handle(self):

        # For later use
        user_name = self.request.payload['user_name']

        # Get data from CRM ..
        crm_data = self.invoke_crm(user_name)

        # .. extract the CRM information we are interested in ..
        user_type = crm_data['UserType']
        account_no = crm_data['AccountNumber']

        # .. get data from Payments ..
        payments_data = self.invoke_payments(user_name, account_no)

        # .. extract the CRM data we are interested in ..
        account_balance = payments_data['ACC_BALANCE']

        # .. optionally, notify the fraud detection system ..
        if self.should_notify_fraud_detection(user_type):
            self.notify_fraud_detection(user_name, account_no)

        # .. now, produce the response for our caller.
        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):

        # Log what we are about to do
        self.logger.info('Invoking CRM; u=%s', user_name)

        # Obtain a connection to CRM ..
        conn = self.out.rest['CRM'].conn

        # .. produce a request for CRM ..
        request = {
            'UserName': user_name,
        }

        # .. invoke the CRM ..
        crm_response = conn.get(self.cid, request)

        # .. return data received from CRM.
        return crm_response.data

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

    def invoke_payments(self, user_name, account_no):

        # Log what we are about to do
        self.logger.info('Invoking Payments; u=%s, a=%s', user_name, account_no)

        # Obtain a connection to Payments ..
        conn = self.out.rest['Payments'].conn

        # .. prepare a request for Payments ..
        request = {
            'ACC_NUM': account_no,
        }

        # .. prepare query string parameters ..
        params = {'USER': user_name}

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

        # .. return data received from Payments.
        return response.data

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

    def notify_fraud_detection(self, user_name, account_no):

        # Log what we are about to do
        self.logger.info('Notifying Fraud detection; u=%s', user_name)

        # Data to send to the Fraud detection system
        data = 'User `{}` accessed `{}`'.format(user_name, account_no)

        # AMQP configuration
        outconn = 'Fraud Detection Connection'
        exchange = '/tutorial'
        routing_key = 'api'

        # Send the message to Fraud Detection
        self.out.amqp.send(data, outconn, exchange, routing_key)

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

    def should_notify_fraud_detection(self, user_type):
        return self.out.redis.conn.get('notify.fraud.{}'.format(user_type))

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

Nous pouvons l'observer immédiatement :

  • Il s'agit d'un code de haut niveau - du point de vue de la mise en œuvre, nous opérons au niveau des dictionnaires Python et de quelques méthodes.

  • Le service est en grande partie indépendant des systèmes sous-jacents qu'il doit intégrer ; il ne s'intéresse pas aux détails des protocoles utilisés dans cet effort d'intégration.

  • Nous utilisons des méthodes simples, comme .get, .post ou .send pour nous connecter à des systèmes externes et c'est à la plateforme Zato de les traduire en invocations réelles, par exemple, le service envoie uniquement des données à "CRM", 'Payments ou 'Fraud Detection Connection' mais il n'a pas besoin de savoir quel point de terminaison REST ou quel courtier AMQP est visé par ce terme.

  • Nous vérifions s'il faut envoyer des notifications au système de détection des fraudes en utilisant self.out.redis - cela pointe vers notre base de données Redis, qui est l'endroit où nous choisissons de garder notre configuration d'exécution dans ce service particulier et nous reviendrons sur le sujet sous peu mais d'abord, puisque le service est prêt, nous allons l'invoquer.

Invoquer le service

Utilisons curl pour accéder au service :

# Assurez-vous d'utiliser ici le mot de passe défini dans la première partie du tutoriel.
$ curl -XPOST http://api:<password>@localhost:11223/api/v1/user \
      -d '{"user_name": "my.user"}'

# Voici notre réponse
{"user_name":"my.user","user_type":"RGV","account_no":"123456","account_balance":"357.9"}
$

Dans les logs du serveur (que l'on peut trouver dans ~/env/qs-1/server1/logs/server.log):

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

Nous avons presque terminé - il ne reste plus qu'à s'assurer que le système de détection des fraudes est notifié en cas de besoin. Pour cela, nous devons discuter de nos options de configuration runtime.

Configuration Runtime

Chaque projet doit conserver sa configuration runtime à un endroit donné. Ce qui est stocké dans cette configuration, ce sont des détails spécifiques au projet, comme des règles ou des conditions commerciales.

En gros, il existe deux types d'endroits où il peut être conservé. Les deux types sont un bon choix et c'est surtout l'architecte qui décide de ce qu'il faut utiliser.

  • Fichiers de configuration
  • Bases de données, telles que Redis, MongoDB, Vault, SQL ou d'autres bases similaires.

Il convient de préciser que dans Zato, les fichiers de configuration sont rechargés automatiquement et mis en cache dans la mémoire vive à chaque fois qu'ils sont mis à jour. Cela signifie que vous pouvez modifier leur contenu sans redémarrer le serveur et sans avoir à redéployer vos services.

Mais, pour montrer quelles autres options sont possibles, dans notre service nous avons décidé de garder la configuration d'exécution dans Redis, ce à quoi l'attribut "self.out.redis.conn" fait référence.

Plus précisément, l'appel de self.out.redis.conn vérifie s'il y a une valeur sous la clé choisie - qui sera ici notify.fraud.RGV car RGV est le user_type renvoyé par le CRM - et s'il y a une valeur, le système de Fraud detection est notifié.

Nous pouvons définir cette clé directement depuis le Dashboard. Allez dans "Key/value DB -> Remote commands" et entrez cette commande puis cliquez sur le bouton "Execute" :

set notify.fraud.RGV 1

Voici le résultat dans le Dashboard. "True" est la réponse de Redis indiquant qu'il a traité la commande avec succès.

Si nous invoquons à nouveau le service, nous remarquerons une nouvelle entrée dans les logs du serveur car, cette fois, nous envoyons également un message AMQP :

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

En ce qui concerne la mise en œuvre, c'est tout, nous avons terminé. Ce que nous avons maintenant, c'est une plateforme d'intégration entièrement fonctionnelle et un service intégrant les clients API avec trois systèmes backend.

Plus de fonctionnalités

Nous n'avons couvert que la partie émergée de l'iceberg en termes d'offres de Zato, énumérons donc rapidement les différentes fonctionnalités disponibles pour la création d'API et de systèmes côté serveur.

  • Courtier en messages avec des sujets de publication/abonnement et des files d'attente de messages.
  • Transfert de fichiers
  • Planificateur de tâches
  • Ouverture de session unique REST et Python
  • Statistiques
  • Canaux : AMQP, HL7, IBM MQ, JSON-RPC, REST, SOAP, WebSockets et ZeroMQ.
  • Connexions sortantes : AMQP, FTP, HL7, IBM MQ, LDAP, Redis, MongoDB, Odoo, REST, SAP RFC, SFTP, SOAP, SQL, WebSockets et ZeroMQ.
  • Plus de types de connexion : AWS S3, Dropbox, Built-in cache, Memcached, ElasticSearch, Solr, Swift, Cassandra, Slack, Telegram, IMAP, SMTP et Twilio
  • Sécurité : Clés API, HTTP Basic Auth, JWT, NTLM, OAuth, RBAC, SSL/TLS, Vault, WS-Security et XPath.
  • CLI et API proposés en tant que services (par exemple, Dashboard lui-même est un client API des services publics de la plateforme).
  • Nombreux petits outils et utilitaires

Jetons également un coup d'œil rapide à deux fonctionnalités en particulier - l'automatisation du déploiement et les tests API.

Automatisation Enmasse

Tout au long du tutoriel, nous avons uniquement utilisé le Dashboard pour configurer les objets Zato, tels que les canaux ou les connexions sortantes. C'est bien, mais lorsqu'il s'agit de déploiement automatisé, nous aimerions avoir un moyen d'exporter notre configuration et de l'importer dans un autre environnement. C'est ce que nous allons faire maintenant.

Parmi les autres options de la ligne de commande, on trouve zato enmasse - cette commande vous permet d'exporter les définitions des objets Zato, d'en fusionner plusieurs et de les importer dans d'autres environnements. Le format de données qu'elle utilise est soit YAML, soit JSON, selon les préférences de chacun.

Le flux de travail typique avec enmasse consiste à exporter progressivement les définitions d'un environnement de développement et à les stocker dans un référentiel de code, en les exportant vers d'autres environnements le moment venu. Comme il s'agit de simples fichiers lisibles par l'homme, il est facile de les modifier ou de remplacer leur contenu pendant le déploiement.

En pratique, on utilise enmasse comme ceci :

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

Le fichier résultant contiendra les définitions de tous les objets trouvés dans la base de données du cluster. Par exemple, nous pouvons trouver notre connexion sortante AMQP parmi eux :

..

outconn_amqp:

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

Après un prétraitement, un tel fichier peut être stocké dans git. De plus, pour réduire les risques de conflits, plusieurs développeurs peuvent avoir chacun leur propre fichier de configuration et enmasse les combinera lorsqu'il sera temps d'importer les objets dans leur ensemble.

Quant à la façon dont l'importation fonctionne, elle est semblable à l'appel précédent. Notez l'utilisation du drapeau --replace-odb-objects - ceci indique à enmasse de mettre à jour les objets déjà existants sur place, sinon il refuserait de continuer au cas où votre intention ne serait pas de remplacer ce qui est déjà stocké dans la base de données opérationnelle (ODB) du cluster.

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

Tests de l'API

Nous avons une implémentation, une configuration et un déploiement automatisé, donc maintenant nous pouvons ajouter des API tests.

Zato dispose d'un outil en ligne de commande appelé apitest qui vous permet d'écrire des tests en anglais pur et nous pouvons l'utiliser pour tester le processus d'intégration développé dans ce tutoriel :

Copions le test ci-dessous pour en faciliter l'analyse :

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
  • Tous les API tests sont écrits en anglais, sans qu'il soit nécessaire de programmer - ce qui permet à tout le monde de participer facilement à leur création.

  • Les tests sont répartis en fonctionnalités et en scénarios. Par exemple, une fonctionnalité peut être "Gestion du compte utilisateur" avec des scénarios individuels testant le CRUD et d'autres parties de la fonctionnalité.

  • Les scénarios peuvent relayer les informations et le contexte entre les tests, par exemple, il est possible d'avoir un scénario d'installation préparant les données à tester, suivi des tests et se terminant par une phase de démantèlement qui nettoie les données de test. C'est comme le cas des tests unitaires, sauf qu'ici nous testons des API entières en utilisant des environnements réels.

  • Il est possible de former une chaîne d'invocations de tests, par exemple un test peut obtenir des données utilisateur, extraire ce qui est nécessaire et le transmettre à un autre test qui a besoin de ces informations.

  • La configuration des tests peut être conservée dans des fichiers externes ou des variables d'environnement, ce qui signifie que les mêmes tests peuvent être utilisés avec des entrées, des sorties ou des environnements différents.

  • Comme apitest fonctionne en ligne de commande, il est facile de l'intégrer dans n'importe quel type de pipeline de développement et de déploiement.

Obtenir un soutien

Ceci conclut le tutoriel - vous êtes maintenant prêt à explorer davantage la plateforme et à construire vos propres solutions API à l'aide de Zato.

Si vous avez des questions, n'oubliez pas que Zato dispose d'un support complet pour les entreprises, y compris une assistance au développement et à la production. Visitez la page support page pour plus de détails et n'hésitez jamais à contacter info@zato.io pour tout type de demande.

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