Les triggers pour échanger des données

David ADAMS

En client-serveur, les triggers s’exécutent sur la machine du serveur et non sur la machine du client. Et le code qui a initié l’action et celui du trigger étant sur des machines différentes, ils ne partagent donc pas les mêmes variables. Cette Note Technique explique comment, à l’aide d’une table utilitaire, partager en toute sécurité des données entre un trigger et le code qui l'a invoqué. ♪

- Titre : Les triggers pour échanger des données

- Auteur : David ADAMS

- Parution : 1 mai 2003

- Licence : Ce document est issu de https://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- I. Résumé

- II. Partager des données entre 4D Client et 4D Serveur

- III. Un enregistrement pour échanger des données

- IV. Suggestions d’implémentation

- V. Utilisations courantes

- VI. Méthodes utilisées

- TriggerMessage_Load

- TriggerMessage_Update

- TriggerMessage_GetMessage

- TriggerMessage_Unload

- VII. Base exemple

I - Résumé

En client-serveur, les triggers s'exécutent sur la machine du serveur et non sur la machine du client. Toute action invoquant un trigger – par exemple sauvegarder un enregistrement – exécute le code du trigger sur le serveur. Et puisque le code qui a initié l'action et celui du trigger sont sur des machines différentes, alors ces codes ne partagent pas les mêmes variables. Ce code testé à présent en 4D monoposte se comporte de manière différente, car le code du trigger s'exécute dans le même process que celui qui a invoqué le trigger. Cette Note Technique explique comment, à l'aide d'une table utilitaire, partager en toute sécurité des données entre un trigger et le code qui l'a invoqué. La technique est efficace en interprété et en compilé, en mono et en client/serveur. Une base exemple est fournie pour vos tests.

Rappel : les chaines d'appel et les triggers

Cette Note technique étudie le comportement des triggers à travers à une base de données d'une bibliothèque. Le système de prêt de cette bibliothèque est fondé sur un module « utilisateurs et groupes » optimisé pour pallier la fonction Utilisateur courant qui ne peut être utilisée. Lors de la procédure de connexion de notre module optimisé, l'ID de l'actuel lecteur est stocké dans une variable interprocess. Le diagramme ci-dessous schématise le code du prêt :

Voici le code de chaque méthode afin de montrer la séquence d'exécution :

Cette chaine d'appel est identique en mono et en client/serveur. Cependant quand la méthode LendItem (Prêt d'article) est exécutée depuis 4D client, la fonction GetCurrentUser (récupération de l'utilisateur courant) génère une erreur pour cause de variable indéfinie ou retourne 0 pour toutes les ID des lecteurs de la bibliothèque. Ce comportement est simple à comprendre : la variable <>Lecteur_ID est initialisée et renseignée sur le client alors que le trigger et la fonction GetCurrentUser sont exécutés sur le serveur. 4D ne partage pas automatiquement les données entre les machines. Voici à nouveau le diagramme avec indication de l'environnement d'exécution de chaque méthode.

II - Partager des données entre 4D Client et 4D Serveur

Le langage de 4D met à disposition trois commandes pour l'échange entre process et entre machines : LIRE VARIABLE PROCESS, ECRIRE VARIABLE PROCESS et enfin VARIABLE PROCESS.

Dès que le développeur rencontre une question comme évoqué ci-avant, il la traitera à l'aide d'une de ces commandes.

Dans la pratique, les utiliser pour apporter une réponse à un problème de trigger est inutilement compliqué :

- des sémaphores doivent être utilisés pour contrôler les accès aux variables des différentes machines. Une simple erreur peut ruiner votre implémentation ;

- sur un serveur les variables process ont une portée différente en compilé ou en interprété. Dans une base interprétée chaque trigger est exécuté avec son jeu de variables process. En compilé, tous les triggers partagent le même jeu de variables. Les échanges entre variables devront prendre en compte cette particularité ;

- les triggers sont prévus pour réaliser des opérations de base sur les données quel que soit le type du client. Les triggers s'exécutent sans savoir qui les a invoqués : un process 4D Client, un process web, une procédure stockée ou une connexion de 4D Open. Beaucoup de ces clients n'ont aucune connaissance de vos variables 4D.

III - Un enregistrement pour échanger des données

Si un process d'une machine client et les triggers d'une machine serveur ne partagent pas les variables process, en revanche ils peuvent se partager des enregistrements. Par exemple, le code du trigger de la table « Item » n'est pas limité aux enregistrements de la table « Item », mais accède à toutes les tables de la base de données. Le process client et le code du trigger sur le serveur partagent la même sélection courante pour chaque table et devraient pointer vers le même enregistrement courant. Le trigger et le process client ne devraient pas avoir la même « copie » d'un enregistrement en particulier, mais ce comportement est facilement gérable.

Pour aller plus loin sur les triggers de 4D, les triggers en cascade, les enregistrements et les sélections, vous pouvez lire « Joy of trigger » à l'adresse :
http://www.4dcompanion.com/downloads/papers/.

La base TriggerMessage utilise une table à deux champs pour échanger les données : les données sont passées via [TriggerMessage]Message_field. Quand un client a besoin de fournir des informations à un trigger, le client charge un enregistrement de la table[TriggerMessage], met à jour le champ Message_field puis sauvegarde l'enregistrement. Cette valeur est dès à présent disponible aux triggers de n'importe quelle table. Dans l'exemple de notre bibliothèque, la variable <>Lecteur_ID devrait renseigner le champ Message_ field sous forme de chaine. La fonction GetCurrentUser peut être réécrite pour fonctionner convenablement.

$0 : = Num ([TriggerMessage] Message_field)

Plutôt que de lire directement du champ [TriggerMessage] Message_field je recommanderai d'utiliser une sous-méthode :

$0 : = Num (TriggerMessage_GetMessage)

Utiliser une sous-méthode comme TriggerMessage_GetMessage minimise la réécriture de code si vous changez la structure de la table [TriggerMessage] et simplifie l'ajout de nouvelles fonctionnalités par exemple la conversion automatique du type d'une donnée.

IV - Suggestions d’implémentation

La table utilisée dans l'exemple TriggerMessage et décrite ci-dessus est suffisante pour la plupart des applications, mais n'est pas idéale pour des systèmes plus complexes. Voici des modifications et améliorations à apporter :

- plusieurs champs pour des messages différents ;

- plusieurs types de champs afin d'éviter trop de conversions inutiles ;

- blobber les valeurs dans un champ ad hoc pour pouvoir envoyer des messages quels que soient leurs complexités et leurs types ;

- placer les valeurs dans un objet ObjectTools puis stocker dans un blob et ainsi faciliter le travail avec des données de types particuliers.

V - Utilisations courantes

Plusieurs utilisations pourront être faites de la technique décrite :

- envoi de pseudoparamètres depuis le client ;

- envoi de valeurs d'état à partir du client ;

- envoi de données structurées depuis le client ;

- retourner des informations supplémentaires d'un trigger comme une description détaillée d'erreur.

Même si cette Note technique se focalise sur l'usage d'un 4D Client comme logiciel client, d'autres types de clients sont aussi possibles. Tout code ou client, par exemple le client 4D open, capable de lire et d'écrire des enregistrements 4D peut utiliser cette technique.

VI - Méthodes utilisées

Voici les différentes méthodes que vous trouverez dans la base exemple :

TriggerMessage_Load

1. ` TriggerMessage_Load : ()

2. ` Cette méthode charge ou crée un enregistrement dans [TriggerMessage]

3. ` Utilisé par le code client et le trigger pour échanger des données

5. C_ENTIER LONG($records_count)

6. C_BOOLEEN($locked_b)

8. ` Trouver un enregistrement existant

9. CHERCHER([TriggerMessage];[TriggerMessage]RecordInUse=Faux)

10. $records_count:=Enregistrements trouves([TriggerMessage])

11. $locked_b:=Enregistrement verrouille([TriggerMessage])

13. Si ($records_count=0) | ($locked_b)

14. ` Créer un nouvel enregistrement plutôt que de réutiliser un ancien

15. CREER ENREGISTREMENT([TriggerMessage])

16. [TriggerMessage]RecordInUse:=Vrai

17. STOCKER ENREGISTREMENT([TriggerMessage])

18. Fin de si ` ($records_count=0) | ($locked_b)

20. ` Fin de la routine

TriggerMessage_Update

1. ` TriggerMessage_Update : ()

3. ` La méthode met à jour l'enregistrement courant

5. C_ALPHA(255;$1)

6. CHARGER ENREGISTREMENT([TriggerMessage])

7. [TriggerMessage]Message_:=$1+Caractere(Retour chariot )+[TriggerMessage]Message_

8. STOCKER ENREGISTREMENT([TriggerMessage])

10. ` Fin de la routine.

TriggerMessage_GetMessage

1. ` TriggerMessage_GetMessage

3. ` Retourne des valeurs de l'enregistrement courant

5. C_TEXTE($0)

6. $0:=[TriggerMessage]Message_

8. ` Fin de la routine

TriggerMessage_Unload

1. ` TriggerMessage_Unload : ()

3. `La méthode marque un enregistrement comme disponible et le libère

5. [TriggerMessage]Message_:=""

6. [TriggerMessage]RecordInUse:=Faux

7. STOCKER ENREGISTREMENT([TriggerMessage])

8. LIBERER ENREGISTREMENT([TriggerMessage])

10. ` Fin de la routine.

VII - Base exemple

Téléchargez la base exemple :

Base pour Windows

Base pour Mac OS