Effacer le filtre
Article
Lorenzo Scalese · Avr 28, 2022
Alors je sais que ça fait un peu longtemps, et je déteste laisser tomber mes fans adorateurs... mais pas assez pour recommencer à écrire. Mais l'attente est terminée et je suis de retour ! Maintenant, profitez de mes mots vraiment magnifiques !
Pour cette série, je vais examiner certains problèmes courants que nous rencontrons au WRC et discuter de certaines solutions communes. Bien sûr, même si vous trouvez une solution ici, vous êtes toujours le bienvenu pour me contacter et exprimer votre gratitude, ou simplement entendre ma voix !
Le problème courant de cette semaine : "Ma requête ne renvoie aucune donnée."
Maintenant, je suppose que vous avez vérifié que votre requête DEVRAIT retourner des données. En d'autres termes, si vous effectuez un "SELECT * FROM MyTable" et que vous n'obtenez aucune donnée, je ne pense pas que votre requête plus compliquée avec des JOINs, une clause WHERE et un GROUP BY le fera également. Donc, si vous avez déterminé que les données se trouvent dans votre tableau, qu'est-ce qui peut bien se passer d'autre ?
1) Vous êtes dans un mauvais espace de noms.
Vous pouvez vous moquer si vous le souhaitez, mais c'est le problème le plus courant. En général, les gens ne signalent pas ce problème, mais il arrive qu'il se présente. Si vous n'obtenez pas de données, la première chose à faire est de vérifier votre espace de noms et, tant que vous y êtes, de vous assurer que vous vous connectez à la bonne instance. Débarrassez-vous d'abord du problème le plus simple.
2) Vous devez construire des indices.
C'est le problème qui perturbe la plupart des gens. Si vous ouvrez votre définition de classe et ajoutez un index, il n'est pas automatiquement construit pour vous. Par conséquent, lorsque vous ajoutez un index, il est disponible pour être utilisé par le compilateur SQL Compiler, mais il ne contient pas de données. Donc quand on regarde, pas de données, boom, requête faite, pas de résultats ! Vous devez appeler ##class().%BuildIndices($LB("")) afin que ce nouvel index soit rempli avec les données appropriées. Attention toutefois à ne pas faire cela sur un système actif ! Pour obtenir des conseils sur la construction d'un index sur un système actif, veuillez contacter le WRC et nous faire part de votre version !
3) Vous êtes dans un mauvais SELECTMODE.
Lorsque vous exécutez une requête, vous pouvez utiliser plusieurs modes : Logical, Display et ODBC. La façon typique de montrer la différence est avec les propriétés %Date. Par exemple, la date d'aujourd'hui est 64295, 01/12/2017, 2017-01-12 dans les modes Logical, Display, et ODBC respectivement. Si vous utilisez le mauvais SELECTMODE, vos requêtes de date ne renverront parfois aucune donnée, même si elles le devraient. Une bonne façon de tester cela est d'exécuter une requête dans le Shell SQL et de définir votre mode de sélection, comme suit :
SAMPLES>d $SYSTEM.SQL.Shell()
SAMPLES>>selectmode = odbc
De cette façon, vous avez un contrôle absolu (et simple) sur le selectmode de votre requête. Vous pouvez également exécuter des requêtes xDBC (ODBC/JDBC) en sachant que nous sommes en mode ODBC.
4) Problèmes de collation
Si vous avez suivi l'un des excellents articles de Brendan concernant la création de votre propre stockage, sachez qu'il existe un problème supplémentaire qui peut vous rattraper. Vous devez vous assurer que la collation que vous avez défini pour votre champ est correctement placé dans votre index. Par conséquent, si votre champ %String a été classé en SQLUPPER, l'index doit être classé en SQLUPPER. Si ce n'est pas le cas, vous pouvez constater que vous n'avez pas de données.
**5) NLS sur CacheTemp**
C'est plutôt rare, mais si vous utilisez une collation NLS différent, vous devez vous assurer que la collation NLS de CACHTEMP correspond à la collation NLS de votre base de données. Le texte officiel est disponible dans les documents ici:
http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL\_basics#GSQL\_basics\_collation\_sqlnls
6) Bug d'InterSystems
Hé, je ne suis pas trop fier de l'admettre. De temps en temps, nous avons des bugs et ça pourrait être le cas. Si vous le pensez, contactez le WRC !
**7) Votre requête comporte un bug.**
Quoi, vous pensiez que vous aviez décroché ! ? Si notre code peut avoir un bug, votre requête peut aussi en avoir un ! Vérifiez encore une fois votre requête !
Ce sont les plus importants. Essayez d'examiner certains des éléments mentionnés ici, et si vous avez des difficultés à les résoudre, n'hésitez pas à contacter le service d'assistance et nous serons heureux de les analyser avec vous !
\---\---\---\---\---\---\---\---\---\---\---\---\---\---\---\---\---\---\---\---\---\---\---\---\-----
**Vous avez besoin d'un TL;DR pour ça !? C'était si court !**
7 raisons expliquant pourquoi votre requête peut ne pas renvoyer de données :
1) Vous êtes dans un mauvais espace de noms.
2) Vous devez construire des indices.
3) Vous êtes dans un mauvais SELECTMODE.
4) Problèmes de collation
5) NLS sur CacheTemp
6) Bug d'InterSystems
7) Votre requête comporte un bug.
**Correction :** Dernière remarque : avez-vous rencontré un problème où une requête ne renvoyant aucune donnée ? Ajoutez votre expérience dans les commentaires !
Article
Guillaume Rongier · Mars 1, 2023
Bonjour à tous,
Nous voici de nouveau réunis. Nouvelle année, nouveau concours, nouveau projet, vieilles raisons.
Triple barre oblique "Triple Slash" est dans la place" !
1999, c'était l'année où j'ai appris à coder, mon premier "IF", mon premier "Bonjour le monde"
Je me souviens encore de mon professeur expliquant à tout le monde dans cette classe le simple "while" et comment nous pouvons déterminer si une condition spécifique a été atteinte @Renato.Banzai vous en souvenez vous ? Le professeur Barbosa, quel homme unique.
Depuis lors, j'aime l'idée de coder, de transformer des idées en projets, en quelque chose d'utile. Mais nous savons tous que pour créer quelque chose, nous devons nous assurer que cela fonctionne ; nous ne nous contentons pas de créer mais nous testons si cela fonctionne et si cela continue à fonctionner si nous ajoutons quelque chose de nouveau.
Et pour être honnête avec chacun d'entre vous, créer des tests, c'est ennuyeux. Du moins pour moi, mais si vous aimez le faire, je n'ai rien contre vous.
En utilisant une mauvaise analogie, je peux dire que la création de méthodes d'essai est comme le nettoyage de votre maison ou le repassage de vos vêtements. C'est ennuyeux, mais c'est nécessaire si vous voulez quelque chose de mieux.
En gardant cela à l'esprit, pourquoi ne pas créer un moyen meilleur et plus facile pour les tests ?
Ainsi, inspirés par le style elixir style and in et par cette idée d' InterSystems Ideas (Merci @Evgeny.Shvarov)! Nous avons essayé d'améliorer le processus de test et de le transformer à nouveau en une tâche agréable.
Nous simplifions le %UnitTest et pour vous montrer comment utiliser TripleSlash pour créer vos tests unitaires, utilisons un exemple simple.
Disons que vous avez la classe et la méthode suivantes, pour lesquelles vous souhaitez écrire un test unitaire :
Class dc.sample.ObjectScript
{
ClassMethod TheAnswerForEverything() As %Integer
{
Set a = 42
Write "Bonjour le monde !",!
Write "C'est InterSystems IRIS avec la version ",$zv,!
Write "L'heure actuelle est : "_$zdt($h,2)
Return a
}
}
Comme vous pouvez le constater, la méthode TheAnswerForEverything() ne fait que retranscrire le nombre 42. Indiquons donc dans la documentation de la méthode comment TripleSlash doit créer un test unitaire pour cette méthode :
/// Une méthode simple à des fins de tests.
///
///
/// Ecrivez ##class(dc.sample.ObjectScript).Test()
/// 42
///
ClassMethod TheAnswerForEverything() As %Integer
{
...
}
Les tests unitaires doivent être entourés de la balise <exemple></exemple>. Vous pouvez ajouter tout type de documentation, mais tous les tests doivent être contenus dans une telle balise.
Maintenant, démarrez une session de terminal IRIS, allez dans l'espace de noms IRISAPP créez une instance de la classe Core en passant le nom de la classe (ou son nom de paquet pour toutes ses classes) et exécutez ensuite la méthode Execute() :
USER>ZN "IRISAPP"
IRISAPP>Do ##class(iris.tripleSlash.Core).%New("dc.sample.ObjectScript").Execute()
TripleSlash interprétera ceci comme "Étant donné le résultat de la méthode Test(), affirme qu'il est égal à 42". Ainsi, une nouvelle classe sera créée dans le test unitaire :
Class iris.tripleSlash.tst.ObjectScript Extends %UnitTest.TestCase
{
Method TestTheAnswerForEverything()
{
Do $$$AssertEquals(##class(dc.sample.ObjectScript).TheAnswerForEverything(), 42)
}
}
Ajoutons maintenant une nouvelle méthode pour tester les autres moyens permettant d'indiquer à TripleSlash comment écrire vos tests unitaires.
Class dc.sample.ObjectScript
{
ClassMethod GuessTheNumber(pNumber As %Integer) As %Status
{
Set st = $$$OK
Set theAnswerForEveryThing = 42
Try {
Throw:(pNumber '= theAnswerForEveryThing) ##class(%Exception.StatusException).%New("Sorry, wrong number...")
} Catch(e) {
Set st = e.AsStatus()
}
Return st
}
}
Comme vous pouvez le constater, la méthode GuessTheNumber() attend un nombre, renvoie $$$OK uniquement si le nombre 42 est transmis ou une erreur pour toute autre valeur. Il faut donc indiquer dans la documentation de la méthode comment TripleSlash doit créer un test unitaire pour cette méthode :
/// Une autre méthode simple à des fins de test.
///
///
/// Exécutez ##class(dc.sample.ObjectScript).GuessTheNumber(42)
/// $$$OK
/// Do ##class(dc.sample.ObjectScript).GuessTheNumber(23)
/// $$$NotOK
///
ClassMethod GuessTheNumber(pNumber As %Integer) As %Status
{
...
}
Exécutez à nouveau la méthode Execute() et vous verrez une nouvelle méthode de test dans la classe de test unitaire iris.tripleSlash.tst.ObjectScript:
Class iris.tripleSlash.tst.ObjectScript Extends %UnitTest.TestCase
{
La méthode TestGuessTheNumber()
{
Do $$$AssertStatusOK(##class(dc.sample.ObjectScript).GuessTheNumber(42))
Do $$$AssertStatusNotOK(##class(dc.sample.ObjectScript).GuessTheNumber(23))
}
}
Actuellement, les assertions suivantes sont disponibles : $$$AssertStatusOK, $$$AssertStatusNotOK et $$$AssertEquals.
TripleSlash nous permet de générer des tests à partir d'exemples de code trouvés dans les descriptions de méthodes. Il vous permet de faire d'une pierre deux coups, en améliorant la documentation de vos classes et en créant l'automatisation des tests.
Remerciements
Une fois encore, nous vous remercions pour tout le soutien de la communauté dans chacune des applications que nous créons.
Si vous avez trouvé notre application intéressante et avez apporté votre contribution, veuillez voter pour iris-tripleslash et nous aider dans cette aventure !
Article
Lorenzo Scalese · Mai 17, 2023
Nos clients ont souvent besoin de configurer HealthShare HealthConnect et IRIS en mode haute disponibilité.
D'autres moteurs d'intégration sur le marché sont souvent présentés comme ayant des configurations de "haute disponibilité", mais ce n'est pas vraiment le cas. En général, ces solutions fonctionnent avec des bases de données externes et donc, si celles-ci ne sont pas configurées en haute disponibilité, lorsqu'un crash de la base de données se produit ou que la connexion à celle-ci est perdue, l'ensemble de l'outil d'intégration devient inutilisable.
Dans le cas des solutions InterSystems, ce problème n'existe pas, car la base de données fait partie intégrante des outils eux-mêmes. Et comment InterSystems a-t-il résolu le problème de la haute disponibilité ? Par des configurations absconses qui pourraient nous entraîner dans une spirale d'aliénation et de folie ? NON ! Chez InterSystems, nous avons écouté et pris en compte vos plaintes (comme nous essayons toujours de le faire ;) ) et nous avons mis la fonction **mirroring** (mise en miroir) à la disposition de tous nos utilisateurs et développeurs.
## Mirroring
Comment fonctionne le **Miroir** ? Le concept lui-même est très simple. Comme vous le savez déjà, IRIS et HealthShare fonctionnent avec un système de journalisation qui enregistre toutes les opérations de mise à jour des bases de données de chaque instance. Ce système de journalisation est celui qui nous aide à récupérer les instances sans perte de données après un crash. Ces fichiers journaux sont envoyés entre les instances configurées en miroir, ce qui permet aux instances configurées en miroir d'être mises à jour en permanence.
## Architecture
Décrivons brièvement l'architecture d'un système configuré en miroir :
* Deux instances configurées en mode basculement :
* Nœud actif : reçoit toutes les opérations régulières de lecture/écriture.
* Nœud passif : en mode lecture, il reçoit de manière synchrone toutes les modifications produites dans le nœud actif.
* 0-14 instances asynchrones : autant d'instances asynchrones que vous souhaitez utiliser; elles peuvent être de deux types :
* DR async (reprise après sinistre) : nœuds en mode lecture qui ne font pas partie du basculement, bien qu'ils puissent être transférés manuellement. Si tel est le cas, ils pourraient être automatiquement transférés vers le nœud principal en cas de défaillance des deux autres nœuds de basculement. La mise à jour de vos données se fait en mode asynchrone, leur fraîcheur n'est donc pas garantie.
* Reporting Asyncs (rapports asynchrones) : Nœuds mis à jour de manière asynchrone pour une utilisation dans des tâches de BI ou d'exploration de données. Ils ne peuvent pas être transférés vers le basculement car des écritures peuvent être effectuées sur les données.
* ISCAgent : Installé sur chaque serveur où se trouve une instance. Il sera chargé de surveiller l'état des instances de ce serveur. C'est un autre moyen de communication entre les Serveurs Miroirs en plus de la communication directe.
* Arbiter : il s'agit d'un ISCAgent installé indépendamment par rapport aux serveurs qui composent le Miroir et qui permet d'augmenter la sécurité et le contrôle des bascules au sein de celui-ci en surveillant les ISCAgents installés et les instances d'IRIS/HealthShare. Son installation n'est pas obligatoire.
Il s'agirait du fonctionnement d'un Miroir formé par un basculement avec seulement deux nœuds :

## Avertissement préalable
Le projet associé à cet article n'a pas de licence active permettant la configuration du miroir. Si vous voulez l'essayer, envoyez-moi un email directement ou ajoutez un commentaire en bas de l'article et je vous contacterai.
## Déploiement dans Docker
Pour cet article, nous allons mettre en place un petit projet dans Docker qui nous permet de mettre en place 2 instances de basculement avec un Arbiter. Par défaut, les images IRIS disponibles pour Docker ont l'ISCAgent déjà installé et configuré, nous pouvons donc sauter cette étape. Il sera nécessaire de configurer le projet associé à l'article à partir d'un code Visual Studio, car cela nous permettra de travailler plus confortablement avec les fichiers du serveur par la suite.
Voyons quelle forme aurait notre docker-compose.yml :
version: '3.3'
services:
arbiter:
container_name: arbiter
hostname: arbiter
image: containers.intersystems.com/intersystems/arbiter:2022.1.0.209.0
init: true
command:
- /usr/local/etc/irissys/startISCAgent.sh 2188
mirrorA:
image: containers.intersystems.com/intersystems/iris:2022.1.0.209.0
container_name: mirrorA
depends_on:
- arbiter
ports:
- "52775:52773"
volumes:
- ./sharedA:/shared
- ./install:/install
- ./management:/management
command:
--check-caps false
--key /install/iris.key
-a /install/installer.sh
environment:
- ISC_DATA_DIRECTORY=/shared/durable
hostname: mirrorA
mirrorB:
image: containers.intersystems.com/intersystems/iris:2022.1.0.209.0
container_name: mirrorB
depends_on:
- arbiter
- mirrorA
ports:
- "52776:52773"
volumes:
- ./sharedB:/shared
- ./install:/install
- ./management:/management
command:
--check-caps false
--key /install/iris.key
-a /install/installer.sh
environment:
- ISC_DATA_DIRECTORY=/shared/durable
hostname: mirrorB
Nous pouvons remarquer que nous avons défini 3 conteneurs :
* **Arbiter** : il correspond à l'ISCAgent (même si l'image s'appelle Arbiter) qui sera déployé pour contrôler les instances IRIS qui formeront le Mirror Failover (basculement miroir). Au démarrage du conteneur, il exécutera un fichier shell qui démarrera l'ISCAgent écoutant sur le port 2188 du conteneur.
* **mirrorA** : conteneur dans lequel l'image IRIS v.2022.1.0.209 sera déployée et que nous configurerons ultérieurement en tant que nœud de basculement primaire.
* **mirrorB** : conteneur dans lequel l'image IRIS v.2022.1.0.209 sera déployée et que nous configurerons ultérieurement en tant que nœud de basculement secondaire.
Lorsque nous exécutons la commande **docker-compose up -d**, les conteneurs définis seront déployés dans notre Docker, et cela devrait ressembler à ceci dans notre Docker Desktop (si nous le faisons à partir de Windows).
.png)
## Configuration du miroir.
Avec nos conteneurs déployés, nous allons procéder à l'accès aux instances que nous allons configurer en miroir, la première sera à l'écoute sur le port 52775 ([mirrorA](http://localhost:52775/csp/sys/UtilHome.csp)) et la seconde sur 52776 ([mirrorB](http://localhost:52776/csp/sys/UtilHome.csp)). L'utilisateur et le mot de passe d'accès seront **superuser** / **SYS**
Du fait que les instances sont déployées dans Docker, nous aurons deux options pour configurer les IP de nos serveurs. La première est d'utiliser directement le nom de nos conteneurs dans la configuration (ce qui est le plus simple) ou de vérifier les IP que Docker a attribuées pour chaque conteneur (en ouvrant la console et en exécutant un ifconfig qui renvoie l'IP attribuée). Pour des raisons de clarté, nous utiliserons pour l'exemple les noms que nous avons donnés à chaque conteneur comme adresse de chacun au sein de Docker.
Tout d'abord, nous allons configurer l'instance que nous utiliserons comme nœud actif du basculement (FailOver). Dans notre cas, ce sera ce que nous avons appelé **mirrorA**.
La première étape consistera à activer le service de mise en miroir, ce qui nous permettra d'accéder au menu de mise en miroir à partir du portail de gestion : **Administration du système** --> **Configuration** --> **Paramètres du miroir** --> **Activer le miroir** **Service** et cochez la case **Service activé** :
.png)
Une fois le service activé, nous pouvons commencer à configurer notre nœud actif. Après avoir activé le service, vous pourrez voir que de nouvelles options ont été activées dans le menu Miroir :
.png)
Dans ce cas, comme nous n'avons pas de configuration de miroir déjà créée, nous devons en créer une nouvelle avec l'option **Créer un miroir**. Lorsque nous accédons à cette option, le portail de gestion ouvrira une nouvelle fenêtre à partir de laquelle nous pourrons configurer notre miroir :
.png)
Examinons de plus près chacune des options :
* **Nom du miroir** : le nom avec lequel nous identifierons notre miroir. Pour notre exemple, nous l'appellerons MIRRORSET.
** Nécessite le SSL/TLS** : pour notre exemple, nous ne configurerons pas de connexion utilisant le SSL/TLS, bien que dans les environnements de production, il serait plus que pratique d'éviter que le fichier journal soit partagé sans aucun type d'encryptage entre les instances. Si vous souhaitez configurer cette connexion, vous avez tous les renseignements nécessaires à l'adresse [URL](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GTLS_mirroring#GTLS_mirroring_configs_creating) de la documentation.
* **Use Arbiter** : cette option n'est pas obligatoire, mais elle est fortement recommandée, car elle ajoute une couche de sécurité à notre configuration de miroir. Pour notre exemple, nous la laisserons cochée et nous indiquerons l'IP dans laquelle nous avons notre Arbiter en cours d'exécution. Pour notre exemple, l'IP sera dans le nom du conteneur **arbiter**.
* **User Virtual IP** : dans les environnements Linux/Unix, cette option est très intéressante car elle nous permet de configurer une IP virtuelle pour notre nœud actif qui sera géré par notre miroir. **Cette IP virtuelle doit appartenir au même sous-réseau que les nœuds de basculement**. Le fonctionnement de l'IP virtuelle est très simple, en cas de défaillance du nœud actif le miroir configurera automatiquement l'IP virtuelle sur le serveur où se trouve le nœud passif à promouvoir. De cette manière, la promotion du nœud passif en nœud actif sera complètement transparente pour les utilisateurs, puisqu'ils continueront à être connectés à la même IP, même si elle sera configurée sur un serveur différent. Si vous souhaitez en savoir plus sur l'IP virtuelle, vous pouvez consulter cette [URL](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_set_config#GHA_mirror_set_virtualip) de la documentation.
Le reste de la configuration peut être laissé tel quel. Sur le côté droit de l'écran, nous verrons les renseignements relatifs à ce nœud dans le miroir :
* **Nom du membre du miroir** : nom de ce membre du miroir, par défaut il prendra le nom du serveur avec le nom de l'instance.
* **Superserver Address** : Adresse IP du super-serveur de ce nœud, dans notre cas, **mirrorA**.
* **Port de l'agent** : port dans lequel l'agent ISCAgent correspondant à ce nœud a été configuré. Par défaut **2188**.
Une fois les champs nécessaires configurés, nous pouvons procéder à la sauvegarde du miroir. Nous pouvons vérifier comment la configuration s'est déroulée à partir du moniteur du miroir (**Opération du système** --> **Moniteur du miroir**).
.png)
Parfait, nous avons notre miroir nouvellement configuré. Comme vous pouvez le constater, seul le nœud actif que nous venons de créer apparaît. Très bien, allons donc ajouter notre nœud passif dans le Failover. Nous accédons au portail de gestion **mirrorB** et au menu Mirror Settings. Comme nous l'avons déjà fait pour l'instance **mirrorA**, nous devons activer le service Mirror. Nous répétons l'opération et dès que les options du menu seront mises à jour, nous choisirons **Join as Failover** (Rejoindre en tant que basculement).
.png)
Nous avons ici l'écran de connexion au miroir. Expliquons brièvement la signification de chacun des champs :
* **Nom du miroir** : nom que nous avons donné au miroir au moment de sa création, dans notre exemple **MIRRORSET**.
* **Adresse de l'agent sur l'autre système** : IP du serveur où l'ISCAgent du nœud actif est déployé, pour nous ce sera **mirrorA.**
* **Port de l'agent** : port d'écoute de l'ISCAgent du serveur dans lequel nous avons créé le miroir. Par défaut **2188**.
* **Nom de l'instance IRIS d'InterSystems** : le nom de l'instance IRIS sur le nœud actif. Dans ce cas, il coïncide avec celui du nœud passif, **IRIS**.
.png)
Après avoir enregistré les données du miroir, nous aurons la possibilité de définir les renseignements relatifs au nœud passif que nous sommes en train de configurer. Examinons à nouveau les champs que nous pouvons configurer pour le nœud passif :
* **Nom du membre du miroir** : nom que le nœud passif prendra dans le miroir. Par défaut, il est formé par le nom du serveur et de l'instance.
**Superserver Address** : Adresse IP du super-serveur dans notre nœud passif. Dans ce cas **mirrorB**.
* Port de l'agent** : port d'écoute de l'agent ISCAgent installé sur le serveur du nœud passif que nous sommes en train de configurer. Par défaut **2188**.
**SSL/TLS Requirement** : non configurable dans cet exemple, nous n'utilisons pas SSL/TLS.
**Adresse privée du miroir** : Adresse IP du nœud passif. Comme nous l'avons vu, lors de l'utilisation de Docker, nous pouvons utiliser le nom du conteneur **mirrorB**.
**Adresse de l'agent** : Adresse IP du serveur où l'ISCAgent est installé. Même chose que précédemment, **mirrorB**.
Nous enregistrons la configuration comme nous l'avons indiqué et nous retournons au moniteur du miroir pour vérifier que nous avons tout configuré correctement. Nous pouvons visualiser le moniteur du nœud actif dans **miroirA** et du nœud passif dans **miroirB**. Examinons les différences entre les deux instances.
Moniteur miroir sur le nœud actif **mirrorA** :
.png)
Moniteur du miroir sur le nœud passif **mirrorB**:.png)
Comme vous pouvez le constater, les renseignements affichés sont similaires, il s'agit essentiellement de changer l'ordre des membres du basculement. Les options sont également différentes, examinons-en quelques-unes :
* Nœud actif **mirrorA** :
* **Set No Failover** (Configurer pas de basculement) : empêche l'exécution du basculement dans le cas d'un arrêt de l'une des instances qui en font partie.
* **Demote other member** (Démonter l'autre membre) : Supprime l'autre membre du basculement (dans ce cas **mirrorB**) de la configuration du miroir.
* Nœud passif **mirrorB** :
* **Stop Mirror On This Member** (Supprimer le miroir sur ce membre) : arrête la synchronisation du miroir sur le membre de basculement (ici **mirrorB**) : Arrête la synchronisation du miroir sur le nœud passif de basculement.
* **Demote To DR Member** (Rétrograder vers le membre DR) : rétrograde ce nœud de la partie du basculement avec sa synchronisation en temps réel vers le mode de reprise après sinistre en mode asynchrone.
Parfait, nous avons déjà nos nœuds configurés, voyons maintenant la dernière étape de notre configuration. Nous avons à décider quelles tableaux feront partie du miroir et à le configurer sur les deux nœuds. Si vous regardez le README.md du projet Open Exchange associé à cet article, vous verrez que nous configurons et déployons deux applications que nous utilisons habituellement pour la formation. Ces applications sont déployées automatiquement lorsque nous démarrons les conteneurs Docker et que les NAMESPACES et les bases de données sont créés par défaut.
La première application est celle de l'entreprises [COMPANY](http://localhost:52775/csp/company/Company.csp) qui nous permet de sauvegarder les dossiers des entreprises et la seconde est [PHONEBOOK](http://localhost:52775/csp/phonebook/Phonebook.AllStart.cls) qui nous permet d'ajouter des contacts personnels liés aux entreprises enregistrées, ainsi que des clients.
Ajoutons une entreprise :
.png)
Nous allons maintenant créer un contact personnel pour l'entreprise précédente :
.png)
Les données relatives à l'entreprise seront enregistrées dans la base de données **COMPANY** et les données relatives au contact dans **PERSONAL**, les deux bases de données étant mappées de manière à être accessibles à partir de l'espace de noms PHONEBOOK. Si nous vérifions les tableaux dans les deux nœuds, nous verrons que dans **mirrorA** nous avons les données de l'entreprise et du contact, mais que dans **mirrorB** il n'y a toujours rien, ce qui est logique.
Les entreprises enregistrées dans **mirrorA:**
.png)
Très bien, procédons à la configuration des bases de données sur notre miroir. Pour ce faire, depuis notre nœud actif (**miroirA**), nous accédons à l'écran d'administration des bases de données locales (**Administrateur système** --> **Configuration** --> **Configuration du système** --> **Bases de données locales**) et nous cliquons sur l'option **Ajouter au miroir**, nous devons sélectionner dans la liste toutes les bases de données que nous voulons ajouter et lire le message qui s'affiche à l'écran :
.png)
Une fois les bases de données ajoutées au miroir à partir du nœud actif, nous avons à faire une sauvegarde de celles-ci ou à copier les fichiers de base de données (IRIS.dat) et à les restaurer sur le nœud passif. Si vous décidez de faire une copie directe des fichiers IRIS.dat, gardez à l'esprit que vous devez figer les écritures dans la base de données à copier, vous pouvez voir les commandes nécessaires dans l'[URL](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCDI_backup#GCDI_backup_methods_ext) de la documentation. Dans notre exemple, il ne sera pas nécessaire de faire une pause, puisque personne d'autre que nous n'écrit dans la base de données.
Avant d'effectuer cette copie des fichiers de la base de données, vérifions l'état du miroir à partir du moniteur du nœud actif :
.png)
Examinons le nœud passif :
.png)
Comme nous pouvons le voir, depuis le nœud passif nous sommes informés que bien que nous ayons 3 bases de données configurées dans le miroir, la configuration n'a pas encore été faite. N'oublions pas que nous devons démonter les bases de données du nœud passif pour pouvoir effectuer la copie et pour cela nous accéderons depuis le portail de gestion à **Configuration du système** --> **Bases de données** et en accédant à chacune d'entre elles nous procéderons à leur démontage.
.png)
Parfait ! Bases de données démontées. Accédons au code du projet associé à l'article depuis Visual Studio Code et constatons que nous avons les dossiers où se trouvent les installations IRIS, **sharedA** pour **mirrorA** et **sharedB** pour **mirrorB**. Accédons aux dossiers où se trouvent les bases de données COMPANY, CUSTOMER et PERSONAL (**/sharedA/durable/mgr**) et procédons à la copie du fichier IRIS.dat de chaque base de données dans le miroir vers les répertoires appropriés du miroirB (**/sharedB/durable/mgr**).
.png)
Une fois la copie terminée, nous montons à nouveau les bases de données **mirrorB** et vérifions l'état des bases de données configurées à partir du moniteur du miroir **mirrorB** :
.png)
Bingo ! Notre miroir a reconnu les bases de données et il ne nous reste plus qu'à les activer et les mettre à jour. Pour ce faire, nous allons cliquer sur l'action **Activer** puis sur **Catchup** (Rattraper), qui apparaîtra après l'activation. Voyons ce qu'il en est :
.png)
PParfait, nos bases de données sont déjà correctement configurées en miroir, si nous consultons la base de données COMPANY nous devrions voir l'enregistrement que nous avons enregistré depuis **mirrorA** auparavant :
.png)
Il est évident que notre base de données COMPANY a l'enregistrement que nous avons saisi précédemment dans **mirrorA**, nous avons copié l'ensemble de la base de données après tout. Ajoutons une nouvelle société à partir de **miroirA** que nous appellerons "Une autre société" et interrogeons à nouveau le tableau de la base de données COMPANY :
.png)
Le voici. Nous aurons juste à nous assurer que nos bases de données configurées en miroir sont en mode lecture seule pour le nœud passif **mirrorB** :
.png)
Et les voici ! en mode R pour la lecture. Bon, nous avons déjà notre miroir configuré et nos bases de données synchronisées. Dans le cas où nous aurions des productions en cours, ce ne serait pas un problème puisque le miroir se charge automatiquement de les gérer, en les démarrant dans le nœud passif en cas de chute dans le nœud actif.
Merci beaucoup à tous ceux qui ont atteint ce stade ! C'était long, mais j'espère que cela vous sera utile.
Article
Irène Mykhailova · Mai 23, 2022
Le type DATE correspond au type de données du produit InterSystems %Date et le type TIME correspond à %Time.
%Date enregistre une date interne (premier élément séparé par une virgule de la variable spéciale [$Horolog](https://docs.intersystems.com/irislatestj/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_vhorolog)), et %Time enregistre l'heure interne (deuxième élément séparé par une virgule de la variable spéciale $Horolog). La logique côté serveur utilise donc la valeur au format (logique) interne, sauf si vous changez le mode d'affichage.
La méthode permettant de modifier le format d'affichage de la date et de l'heure internes dans la logique côté serveur dépend de la méthode d'exploitation.
Dans les exemples suivants, nous utiliserons le tableau Sample.Person.
(L'exemple d'exécution de la commande est présenté pour une instruction SELECT, mais il peut également être écrit pour une instruction de mise à jour.)
Pour essayer IRIS/IRIS for Health, téléchargez la documentation à partir de ([Télécharger des échantillons à utiliser avec InterSystems IRIS](https://docs.intersystems.com/irislatestj/csp/docbook/DocBook.UI.Page.cls?KEY=ASAMPLES)),
ou à partir de [Articles connexes (téléchargement de la définition de classe de l'échantillon (Sample.Person) et création de données d'échantillon)](https://jp.community.intersystems.com/node/495332), veuillez commencer par importer la classe Sample.Person et créer les données d'exemple.
Si vous essayez Caché/Ensemble, utilisez Sample.Person dans l'espace de noms SAMPLES.
* * *
(1) Si vous utilisez l'Embedded SQL
Pour changer le format d'affichage à l'aide d'Embedded SQL, utilisez **#sqlcomple select**.
Les valeurs suivantes peuvent être spécifiées.
* Logical (par défaut)
* Display
* ODBC
* Runtime
Documentation (IRIS) : [Compilation du SQL intégré et du préprocesseur de macros【IRIS】](https://docs.intersystems.com/irislatestj/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_esql#GSQL_esql_mpp)
Documentation : [Compilation du SQL intégré et du préprocesseur de macros](https://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_esql#GSQL_esql_mpp)
#sqlcompile select=ODBC&sql(declare C1 Cursor for select ID,Name,DOB into :pid,:name,:dob from Sample.Person where ID<=5)&sql(open C1)for { &sql(fetch C1) if SQLCODE'=0 { quit } //Exemple d'affichage)1-Mastrolito,Susan T.-2013-01-01 write pid,"-",name,"-",dob,!}&sql(close C1)
(2) Si vous utilisez le Dynamic SQL
Pour changer le format d'affichage en SQL dynamique à l'aide de [%SQL.Statement](http://docs.intersystems.com/irislatestj/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&CLASSNAME=%25SQL.Statement), utilisez la propriété %SelectMode.
Cette propriété doit être définie avant l'exécution de %Execute().
Les valeurs qui peuvent être définies sont les suivantes.
* 0: mode logique
* 1: mode ODBC
* 2: mode d'affichage
SAMPLES>set sql="select ID,Name,DOB from Sample.Person where ID <= 5" SAMPLES>set stmt=##class(%SQL.Statement).%New() SAMPLES>set st=stmt.%Prepare(sql) SAMPLES>set rset=stmt.%Execute() SAMPLES>do rset.%Display()ID Name DOB1 Gallant,Yan N. 421462 Waal,Umberto G. 453593 Jenkins,Sam A. 374044 Marks,Milhouse B. 520435 Hernandez,Phyllis W. 64590 5 Rows(s) AffectedSAMPLES>
(3) Lorsque vous utilisez une requête de classe
Pour changer le format d'affichage dans une requête de classe, utilisez le paramètre de définition de la requête : **SELECTMODE**.
Les valeurs qui peuvent être spécifiées sont les suivantes
* RUNTIME (par défaut)
* LOGICAL
* DISPLAY
* ODBC
Voici un exemple de définition.
Query NewQuery1() As %SQLQuery(SELECTMODE = "ODBC")<br>{<br>select ID,Name,DOB from Sample.Person where ID<=5<br>}
(4) Comment changer le format d'affichage des processus en cours
L'objet système **$SYSTEM.SQL.SetSelectMode()** peut être utilisé pour modifier le format d'affichage du processus en cours.
Les arguments et les valeurs de retour sont les suivants.
* Spécifiez 0 (logique), 1 (ODBC) ou 2 (affichage) comme premier argument.
* Le second argument est un argument de type pass-by-reference dont le résultat d'exécution est défini par %Status.
* La valeur de retour est le numéro du mode d'affichage en cours.
Veuillez vous référer à la page du document ci-dessous pour plus de détails.
[Les bases d'InterSystems SQL - Options d'affichage des données【IRIS】](https://docs.intersystems.com/irislatestj/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_basics#GSQL_basics_selectmode)
[Options d'affichage de CachéSQL Basics_Data](https://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_basics#GSQL_basics_selectmode)
// Changement du format par défaut au format ODBCSAMPLES>set cm=$system.SQL.SetSelectMode(1,.st)SAMPLES>set sql="select ID,Name,DOB from Sample.Person where ID <= 5"SAMPLES>set stmt=##class(%SQL.Statement).%New()SAMPLES>set st=stmt.%Prepare(sql)SAMPLES>set rset=stmt.%Execute()SAMPLES>do rset.%Display()ID Name DOB1 Gallant,Yan N. 1956-05-232 Waal,Umberto G. 1965-03-103 Jenkins,Sam A. 1943-05-304 Marks,Milhouse B. 1983-06-285 Hernandez,Phyllis W. 2017-11-03 5 Rows(s) AffectedSAMPLES>
※Après avoir changé le format d'affichage d'un processus, si le format d'affichage est modifié pour chaque méthode d'exécution SQL, le dernier format d'affichage spécifié sera utilisé.
(5) Comment convertir le format d'affichage à l'aide de fonctions ObjectScript
Une autre méthode consiste à utiliser les fonctions de conversion d'affichage d'ObjectScript pour convertir le format interne en format d'affichage.
Pour les fonctions de datation,
Affichage -> Format interne $ZDATEH(yyyymmdd,8) ou $ZDATE(yyyy-mm-dd,3)
Interne -> Pour obtenir le résultat du format d'affichage YYYYYMMDD : $ZDATE(+$Horolog,8)
Si vous voulez obtenir le résultat de YYYYY-MM-DD : $ZDATEH(+$H,3), dans la fonction horaire
Affichage -> format interne $ZTIMEH("HH:MM:SS")
Interne -> Si vous voulez obtenir le résultat au format d'affichage HH:MM:SS : $ZTIMEH($piece($Horolog,"",2)), il existe également les fonctions $ZDATETIME() et $ZDATETIMEH() pour manipuler la date et l'heure.
Vous trouverez plus de détails sur les fonctions de date dans des documents suivants.
[ObjectScript fonction【IRIS】](https://docs.intersystems.com/irislatestj/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_FUNCTIONS)
[ObjectScript fonction](https://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_FUNCTIONS)
SAMPLES>write $horolog63895,34979SAMPLES>write $ZDATE(+$horolog,8) /Conversion au format yyyymmdd>63895SAMPLES>write $ZDATEH("2015-12-09",3) // Conversion du format yyyy-mm-dd en format interne63895SAMPLES>write $ZTIME($piece($horolog,",",2)) // Conversion du format interne en format horaire 09:44:16SAMPLES>write $ZTIMEH("10:01:11") // Conversion de l'heure d'affichage au format interne36071SAMPLES>write $ZDATETIME($horolog,8) // Conversion date/heure avec $horolog20151209 09:45:15SAMPLES>write $ZDATETIME($horolog,3)2015-12-09 09:45:16SAMPLES>
Article
Guillaume Rongier · Août 31, 2022
Nous continuons à observer les possibilités de Django, et son utilisation avec IRIS. Dans la [première partie](https://fr.community.intersystems.com/post/d%C3%A9couvrir-django-partie-1) nous avons regardé comment définir des modèles et se connecter à des tableaux déjà existants dans IRIS, dans la [suite](https://fr.community.intersystems.com/post/d%C3%A9couvrir-django-partie-2) nous avons étendu le portail d'administration intégré de Django, avec la possibilité de voir quelles données nous avons dans ces modèles, avec des filtres, l'édition et même la pagination.
Il est temps de passer à l'action, nous allons maintenant créer une API REST, sur Django, basée sur les mêmes données, que nous avons utilisées auparavant à partir du paquet posts-and-tags.
Pour ce faire, nous utiliserons [Django REST Framework](https://www.django-rest-framework.org/)

Le cadre REST de Django est une boîte à outils puissante et flexible permettant de créer des API Web.
Quelques raisons pour lesquelles vous pourriez vouloir utiliser le cadre REST :
* L'API navigable sur le Web est un avantage considérable pour vos développeurs.
* Politiques d'authentification comprenant des paquets pour OAuth1a et OAuth2.
* Sérialisation qui prend en charge les sources de données ORM et non ORM.
* Personnalisable jusqu'en bas - utilisez simplement des visualisations régulières basées sur des fonctions si vous n'avez pas besoin des fonctionnalités les plus puissantes.
* Une documentation complète et un support communautaire important.
* Utilisé par des entreprises de renommée internationale, telles que Mozilla, Red Hat, Heroku et Eventbrite, qui lui font confiance.
Tout d'abord, nous devons mettre à jour notre fichier requirements.txt avec les dépendances
# Django itself
django>=4.0.0
# Pilote InterSystems IRIS pour Django, et pilote DB-API d'InterSystems
django-iris=>0.1.13
https://raw.githubusercontent.com/intersystems-community/iris-driver-distribution/main/DB-API/intersystems_irispython-3.2.0-py3-none-any.whl
# Cadre REST de Django et ses dépendances facultatives
djangorestframework>=3.4.4
# Support du format Markdown pour l'API navigable.
markdown>=3.0.0
# Support de filtrage
django-filter>=1.0.1
Et installez-les
python install -r requirements.txt
## Première version de l'API
Pour cela, mettez à jour le fichier _urls.py_. Ici on dit, cela de la racine pour notre api comme **_api/_**, donc toutes les demandes à http://localhost:8000/api/ seront utilisées comme racine pour nos demandes API
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
router = routers.DefaultRouter()
urlpatterns = [
path('api/', include(router.urls)),
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
]
Le cadre Django REST a une interface utilisateur intégrée pour les API, lorsque le serveur fonctionne en mode de développement avec DEBUG=True dans settings.py. Et nous pouvons ouvrir cette URL
.png)
Tout fonctionne, même sans rien définir, juste en connectant ce cadre à l'url. Il supporte l'autorisation, pour les requêtes nécessitant une autorisation.
$ curl http://127.0.0.1:8000/api/
{}
Définissez une API pour le projet, nous utiliserons au minimum quelques fonctionnalités du cadre REST.
* Les sérialiseurs permettent de convertir des données complexes, telles que des querysets et des instances de modèles, en types de données Python natifs qui peuvent ensuite être facilement convertis en `JSON`, `XML` ou d'autres types de contenu. Les sérialiseurs fournissent également une désérialisation, permettant aux données analysées d'être reconverties en types complexes, après avoir validé les données entrantes.
* ViewSet - permet de combiner la logique d'un ensemble de visualisations connexes dans une seule classe
Ajoutons un point de terminaison pour nos messages de notification. Très simple, pourtant. Regardez le contenu mis à jour de **_urls.py _**
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers, serializers, viewsets
from .models import CommunityPost
router = routers.DefaultRouter()
class CommunityPostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
# classe avec modèle
model = CommunityPost
# liste des champs à afficher, ou uniquement '__all__'
fields = '__all__'
# les ViewSets définissent le comportement de la visualisation.
class CommunityPostViewSet(viewsets.ModelViewSet):
queryset = CommunityPost.objects.all()
serializer_class = CommunityPostSerializer
# connexion à l'API
router.register(r'posts', CommunityPostViewSet)
urlpatterns = [
path('api/', include(router.urls)),
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
]
Et il apparaît maintenant dans l'interface web
.png)
Il suffit de cliquer sur le lien ici, et vous obtiendrez la réponse à cette question
.png)
Faites défiler jusqu'à la fin, et vous trouverez un formulaire généré pour les nouveaux éléments, qui peuvent être ajoutés par requête POST. Tous les champs sont adaptés au type de propriétés
.png)
Dans la liste des éléments, vous pouvez cliquer sur l'URL de n'importe quel élément, et voir ceci. Seulement cet élément comme réponse, et un formulaire d'édition avec une requête PUT
.png)
## Authentication
Et vous pouvez déjà changer les données, par PUT ou POST. Le besoin d'autorisation n'est pas encore activé. Le cadre REST offre diverses combinaisons d'authentifications à utiliser. Ainsi, vous pouvez ouvrir certaines ressources pour un accès anonyme en lecture seule. Et s'authentifier pour pouvoir effectuer des modifications. Ou fermer complètement tout accès. Il suffit de configurer l'accès en lecture seule pour les anonymes, et de demander une authentification pour toute modification. Pour ce faire, il suffit d'ajouter ces lignes à **_settings.py_**
REST_FRAMEWORK = {
# Utilisez les permissions standard de Django `django.contrib.auth`,
# ou autorisez l'accès en lecture seule pour les utilisateurs non authentifiés.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
],
}
Avec ceci, vous ne verrez plus le formulaire jusqu'à ce que vous vous connectiez, avec le nom d'utilisateur et le mot de passe d'instance créés précédemment pour l'administration de Django.
## Pagination
Par défaut, il n'y a pas de pagination, mais nous pouvons facilement l'ajouter à toute requête de liste. Mettez à jour la variable **REST_FRAMEWORK** dans **_settings.py_**. Configurer la classe de pagination et la taille de page par défaut
REST_FRAMEWORK = {
...
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 10,
...
}
En conséquence, cela a légèrement modifié le JSON résultant, en ajoutant des éléments pertinents, tels que les liens vers les pages suivantes et précédentes, ainsi que la quantité de tous les éléments. Et avec l'interface Web, vous pouvez parcourir les pages.
.gif)
## Filtrage et recherche
Il est également assez simple d'ajouter des capacités de filtrage et de recherche. Mettez à jour la variable **REST_FRAMEWORK** dans **_settings.py_**.
REST_FRAMEWORK = {
...
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
],
...
]
Et mettez à jour CommunityPostViewSet avec une liste de champs pour le filtrage et la recherche
class CommunityPostViewSet(viewsets.ModelViewSet):
queryset = CommunityPost.objects.all()
serializer_class = CommunityPostSerializer
filterset_fields = ['posttype', 'lang', 'published']
search_fields = ['name',]
Et cela fonctionne directement depuis l'interface Web
.gif)
Et finalement, nous disposons d'une API REST entièrement fonctionnelle, juste pour cette seule ressource, pour le moment. C'est assez simple pour le moment, mais il est possible de faire beaucoup de personnalisation, de connecter d'autres ressources et de les lier.
Article
Iryna Mykhailova · Oct 11, 2023
Nous poursuivons notre série d'articles basés sur l'application QuinielaML.
.png)
Dans cet article, je décrirai comment travailler avec la fonctionnalité Embedded Python disponible dans les produits InterSystems.
Embedded Python nous permet d'utiliser Python comme langage de programmation dans nos productions, en profitant de toutes les fonctionnalités qu'offre Python. [Ici](http://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_epython) vous pouvez trouver plus d'informations à ce sujet.
Tout d'abord, rappelons la nature du concepteur de l'architecture de notre projet :

## Problème à résoudre
Dans notre cas, il nous faut obtenir les résultats historiques des matches de première et de deuxième division depuis 2000. Nous avons trouvé un site web [BDFutbol] (http://www.bdfutbol.com/es/index.html) qui nous fournit toutes ces données, et le web scraping semble donc la solution la plus appropriée.
### Qu'est-ce que le web scraping ?
Le web scraping est une technique qui consiste à capturer automatiquement des informations à partir de pages web en simulant la navigation de la même manière que le ferait un être humain.
Pour réaliser le web scraping, nous devons chercher deux types de bibliothèques, la première qui nous permet d'invoquer les URL à partir desquelles nous voulons obtenir l'information et la seconde qui nous permet de parcourir la page web capturée et d'en extraire l'information nécessaire. Pour le premier cas, nous utiliserons la bibliothèque [**requests**](http://pypi.org/project/requests/), tandis que pour le second, nous avons trouvé [**beautifulsoup4**](http://pypi.org/project/beautifulsoup4/), vous pouvez consulter sa [documentation](http://www.crummy.com/software/BeautifulSoup/bs4/doc/).
## Configuration d'Embedded Python avec Docker
Pour utiliser les bibliothèques Python de notre instance IRIS dans Docker, il faut ajouter les commandes suivantes dans notre fichier Docker :
RUN apt-get update && apt-get install -y python3
RUN apt-get update && \
apt-get install -y libgl1-mesa-glx libglib2.0-0
A l'aide de ces commandes, nous installons Python dans notre conteneur, puis nous installons les bibliothèques nécessaires que nous avons enregistrées dans le fichier **requirements.txt**.
beautifulsoup4==4.12.2
requests==2.31.0
Pour son installation, il suffira d'ajouter la commande suivante dans notre Dockerfile :
Article
Lucas Enard · Sept 12, 2022
# 1. Fhir-client-java
Ceci est un client fhir simple en java pour s'exercer avec les ressources fhir et les requêtes CRUD vers un serveur fhir.
Notez que pour la majeure partie, l'autocomplétion est activée.
[GitHub](https://github.com/LucasEnard/fhir-client-java)
- [1. Fhir-client-java](#1-fhir-client-java)
- [2. Préalables](#2-préalables)
- [3. Installation](#3-installation)
- [3.1. Installation pour le développement](#31-installation-pour-le-développement)
- [3.2. Portail de gestion et VSCode](#32-portail-de-gestion-et-VSCode)
- [3.3. Avoir le dossier ouvert à l'intérieur du conteneur](#33-avoir-le-dossier-ouvert-à-l'intérieur-du-conteneur)
- [4. Serveur FHIR](#4-serveur-FHIR)
- [5. Présentation pas à pas](#5-présentation)
- [5.1. Partie 1](#51-partie-1)
- [5.2. Partie 2](#52-partie-2)
- [5.3. Partie 3](#53-partie-3)
- [5.4. Partie 4](#54-partie-4)
- [5.5. Conclusion de la présentation](#55-conclusion-de-la-présentation)
- [6. Comment commencer le codage](#6-comment-commencer-le-codage)
- [7. Ce qu'il y a dans le référentiel](#7-ce-qu'il-y-a-dans-le-référentiel)
- [7.1. Dockerfile](#71-dockerfile)
- [7.2. .vscode/settings.json](#72-vscodesettingsjson)
- [7.3. .vscode/launch.json](#73-vscodelaunchjson)
# 2. Préalables
Assurez-vous que [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) et [Docker desktop](https://www.docker.com/products/docker-desktop) sont installé.
Déjà installé dans le conteneur :
[Hapi Fhir modèle et client](https://hapifhir.io/hapi-fhir/docs/getting_started/introduction.html)
# 3. Installation
## 3.1. Installation pour le développement
Clone/git tire le repo dans n'importe quel répertoire local, par exemple comme indiqué ci-dessous :
```
git clone https://github.com/LucasEnard/fhir-client-java.git
```
Ouvrez le terminal dans ce répertoire et exécutez :
```
docker build .
```
## 3.2. Portail de gestion et VSCode
Ce référentiel est prêt pour [CodeVS](https://code.visualstudio.com/).
Ouvrez le dossier `fhir-client-java` cloné localement dans VS Code.
Si vous y êtes invité (coin inférieur droit), installez les extensions recommandées.
## 3.3. Avoir le dossier ouvert à l'intérieur du conteneur
Vous pouvez être *à l'intérieur* du conteneur avant de coder si vous le souhaitez.
Pour cela, il faut que docker soit activé avant d'ouvrir VSCode.
Ensuite, dans VSCode, lorsque vous y êtes invité ( coin inférieur droit ), rouvrez le dossier à l'intérieur du conteneur afin de pouvoir utiliser les composants python qu'il contient.
La première fois que vous effectuez cette opération, cela peut prendre plusieurs minutes, le temps que le conteneur soit préparé.
Si vous n'avez pas cette option, vous pouvez cliquer dans le coin inférieur gauche et cliquer sur `Ouvrir à nouveau dans le conteneur` puis sélectionner `De Dockerfile`
[Plus d'informations ici](https://code.visualstudio.com/docs/remote/containers)

En ouvrant le dossier à distance, vous permettez à VS Code et à tous les terminaux que vous ouvrez dans ce dossier d'utiliser les composants java dans le conteneur.
# 4. Serveur FHIR
Pour réaliser cette présentation, vous aurez besoin d'un serveur FHIR.
Vous pouvez soit utiliser le vôtre, soit vous rendre sur le site [Essai FHIR gratuit d'InterSystems](https://portal.live.isccloud.io) et suivre les étapes suivantes pour le configurer.
En utilisant notre essai gratuit, il suffit de créer un compte et de commencer un déploiement, puis dans l'onglet `Overview` vous aurez accès à un endpoint comme `https://fhir.000000000.static-test-account.isccloud.io` que nous utiliserons plus tard.
Ensuite, en allant dans l'onglet d'informations d'identification `Credentials`, créez une clé api et enregistrez-la quelque part.
C'est maintenant terminé, vous avez votre propre serveur fhir pouvant contenir jusqu'à 20 Go de données avec une mémoire de 8 Go.
# 5. Présentation pas à pas
La présentation pas à pas du client se trouve à `src/java/test/Client.java`.
Le code est divisé en plusieurs parties, et nous allons couvrir chacune d'entre elles ci-dessous.
## 5.1. Partie 1
Dans cette partie, nous connectons notre client à notre serveur en utilisant Fhir.Rest.
```java
// Partie 1
// Créer un contexte en utilisant FHIR R4
FhirContext ctx = FhirContext.forR4();
// créer un en-tête contenant la clé api pour le httpClient
Header header = new BasicHeader("x-api-key", "api-key");
ArrayList headers = new ArrayList();
headers.add(header);
// créer un constructeur httpClient et lui ajouter l'en-tête
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setDefaultHeaders(headers);
// créer un httpClient à l'aide du constructeur
CloseableHttpClient httpClient = builder.build();
// Configurer le httpClient au contexte en utilisant la fabrique
ctx.getRestfulClientFactory().setHttpClient(httpClient);
// Créer un client
IGenericClient client = ctx.newRestfulGenericClient("url");
```
Afin de vous connecter à votre serveur, vous devez modifier la ligne :
```java
Header header = new BasicHeader("x-api-key", "api-key");
```
Et cette ligne aussi :
```java
IGenericClient client = ctx.newRestfulGenericClient("url");
```
L`'url'` est un point de terminaison tandis que l'`"api-key"` est la clé d'api pour accéder à votre serveur.
Notez que si **vous n'utilisez pas** un serveur InterSystems, vous pouvez vérifier comment autoriser vos accès si nécessaire.
Comme ça, nous avons un client FHIR capable d'échanger directement avec notre serveur.
## 5.2. Partie 2
Dans cette partie, nous créons un Patient en utilisant Fhir.Model et nous le complétons avec un HumanName, en suivant la convention FHIR, `use` et `family` sont des chaînes et `given` est une liste de chaînes. De la même manière, un patient peut avoir plusieurs HumanNames, donc nous devons mettre notre HumanName dans une liste avant de le mettre dans notre patient nouvellement créé.
```java
// Partie 2
// Créer un patient et lui ajouter un nom
Patient patient = new Patient();
patient.addName()
.setFamily("FamilyName")
.addGiven("GivenName1")
.addGiven("GivenName2");
// Voir aussi patient.setGender ou setBirthDateElement
// Créer le patient ressource sur le serveur
MethodOutcome outcome = client.create()
.resource(patient)
.execute();
// Enregistrez l'ID que le serveur a attribué
IIdType id = outcome.getId();
System.out.println("");
System.out.println("Created patient, got ID: " + id);
System.out.println("");
```
Après cela, nous devons sauvegarder notre nouveau Patient sur notre serveur en utilisant notre client.
Notez que si vous lancez `Client.java` plusieurs fois, plusieurs Patients ayant le nom que nous avons choisi seront créés. C'est parce que, suivant la convention FHIR, vous pouvez avoir plusieurs Patients avec le même nom, seul l' `id` est unique sur le serveur.
Vérifier [the doc](https://docs.fire.ly/projects/Firely-NET-SDK/client/search.html#searching) pour avoir plus d'information.
Nous conseillons donc de commenter la ligne après le premier lancement.
## 5.3. Partie 3
Dans cette partie, nous avons un client qui recherche un patient nommé d'après celui que nous avons créé précédemment.
```java
// Partie 3
// Rechercher un patient unique avec le nom de famille exact "NomDeFamille" et le prénom exact "Prénom1"
patient = (Patient) client.search()
.forResource(Patient.class)
.where(Patient.FAMILY.matchesExactly().value("FamilyName"))
.and(Patient.GIVEN.matchesExactly().value("GivenName1"))
.returnBundle(Bundle.class)
.execute()
.getEntryFirstRep()
.getResource();
// Créer une télécommunication pour le patient
patient.addTelecom()
.setSystem(ContactPointSystem.PHONE)
.setUse(ContactPointUse.HOME)
.setValue("555-555-5555");
// Changer le prénom du patient en un autre
patient.getName().get(0).getGiven().set(0, new StringType("AnotherGivenName"));
// Mettre à jour le patient de ressource sur le serveur
MethodOutcome outcome2 = client.update()
.resource(patient)
.execute();
```
Une fois que nous l'avons trouvé, nous ajoutons un numéro de téléphone à son profil et nous changeons son prénom en un autre.
Maintenant nous pouvons utiliser la fonction de mise à jour de notre client pour mettre à jour notre patient sur le serveur.
## 5.4. Partie 4
Dans cette section, nous voulons créer une observation pour notre patient. Pour ce faire, nous avons besoin de son identifiant, qui est son identifiant unique.
A partir de là, nous remplissons notre observation et ajoutons comme sujet, l'identifiant de notre Patient.
```java
// Partie 4
// Créer un CodeableConcept et le remplir
CodeableConcept codeableConcept = new CodeableConcept();
codeableConcept.addCoding()
.setSystem("http://snomed.info/sct")
.setCode("1234")
.setDisplay("CodeableConceptDisplay");
// Créer une quantité et la remplir
Quantity quantity = new Quantity();
quantity.setValue(1.0);
quantity.setUnit("kg");
// Créer une catégorie et la remplir
CodeableConcept category = new CodeableConcept();
category.addCoding()
.setSystem("http://snomed.info/sct")
.setCode("1234")
.setDisplay("CategoryDisplay");
// Créer une liste de CodeableConcepts et y placer des catégories
ArrayList codeableConcepts = new ArrayList();
codeableConcepts.add(category);
// Créer une observation
Observation observation = new Observation();
observation.setStatus(Observation.ObservationStatus.FINAL);
observation.setCode(codeableConcept);
observation.setSubject(new Reference().setReference("Patient/" + ((IIdType) outcome2.getId()).getIdPart()));
observation.setCategory(codeableConcepts);
observation.setValue(quantity);
System.out.println("");
System.out.println("Created observation, reference : " + observation.getSubject().getReference());
System.out.println("");
// Créer l'observation de ressource sur le serveur
MethodOutcome outcome3 = client.create()
.resource(observation)
.execute();
// Imprimer la réponse du serveur
System.out.println("");
System.out.println("Created observation, got ID: " + outcome3.getId());
System.out.println("");
```
Ensuite, nous enregistrons notre observation à l'aide de la fonction de création.
## 5.5. Conclusion de la présentation
Si vous avez suivi ce parcours, vous savez maintenant exactement ce que fait Client.java, vous pouvez le lancer et vérifier votre Patient et votre Observation nouvellement créés sur votre serveur.
Pour le lancer, ouvrez un terminal VSCode et entrez :
```
dotnet run
```
Vous devriez voir des informations sur le Patient créé et son observation.
Si vous utilisez un serveur Intersystems, allez à `API Deployement`, autorisez-vous avec la clé api et d'ici vous pouvez OBTENIR par id le patient et l'observation que nous venons de créer.
# 6. Comment commencer le codage
Ce référentiel est prêt à être codé dans VSCode avec les plugins InterSystems.
Ouvrez `Client.java` pour commencer à coder ou utiliser l'autocomplétion.
# 7. Ce qu'il y a dans le référentiel
## 7.1. Dockerfile
Un dockerfile pour créer un dot net env pour que vous puissiez travailler.
Utilisez `docker build .` pour construire et rouvrir votre fichier dans le conteneur pour travailler à l'intérieur de celui-ci.
## 7.2. .vscode/settings.json
Fichier de paramètres.
## 7.3. .vscode/launch.json
Fichier de configuration si vous voulez déboguer
Article
Guillaume Rongier · Mars 16, 2022
Cette formation s'adresse aux débutants qui souhaitent découvrir le framework IRIS Interoperability. Nous utiliserons Docker et VSCode.
GitHub : https://github.com/grongierisc/formation-template
# 1. **Formation Ensemble / Interoperability**
Le but de cette formation est d'apprendre le cadre d'interopérabilité d'InterSystems, et en particulier l'utilisation de :
* Productions
* Messages
* Opérations commerciales
* Adaptateurs
* Processus métier
* Services commerciaux
* Services et opérations REST
**TABLE DES MATIÈRES :**
- [1. **Ensemble / Formation Interoperability **](#1-ensemble--interoperability-formation)
- [2. Framework](#2-framework)
- [3. Adapter le framework](#3-adapting-the-framework)
- [4. Prérequis](#4-prerequisites)
- [5. Mise en place](#5-setting-up)
- [5.1. Conteneurs Docker](#51-docker-containers)
- [5.2. Portail de gestion](#52-management-portal)
- [5.3. Sauvegarde de la progression](#53-saving-progress)
- [6. Productions](#6-productions)
- [7. Opérations](#7-operations)
- [7.1. Création de notre classe de stockage](#71-creating-our-storage-class)
- [7.2. Création de notre classe de messages](#72-creating-our-message-class)
- [7.3. Création de notre opération](#73-creating-our-operation)
- [7.4. Ajout de l'opération à la production](#74-adding-the-operation-to-the-production)
- [7.5. Test](#75-testing)
- [8. Processus métier](#8-business-processes)
- [8.1. BP simple](#81-simple-bp)
- [8.1.1. Création du processus](#811-creating-the-process)
- [8.1.2. Modifier le contexte d'un BP](#812-modifying-the-context-of-a-bp)
- [8.2. BP lisant les lignes CSV](#82-bp-reading-csv-lines)
- [8.2.1. Création d'une carte d'enregistrement](#821-creating-a-record-map)
- [8.2.2. Création d'une transformation de données](#822-creating-a-data-transformation)
- [8.2.3. Ajout de la transformation des données au processus métier](#823-adding-the-data-transformation-to-the-business-process)
- [8.2.4. Configuration de la production](#824-configuring-production)
- [8.2.5. Test de](#825-testing)
- [9. Accéder à une base de données externe à l'aide de JDBC](#9-getting-access-to-an-extern-database-using-jdbc)
- [9.1. Création de notre nouvelle opération](#91-creating-our-new-operation)
- [9.2. Configurer la production](#92-configuring-the-production)
- [9.3. Test](#93-testing)
- [9.4. Exercice](#94-exercise)
- [9.5. Solution](#95-solution)
- [10. Service REST](#10-rest-service)
- [10.1. Créer le service](#101-creating-the-service)
- [10.2. Ajouter notre BS](#102-adding-our-bs)
- [10.3. Tester](#103-testing)
- [Conclusion](#conclusion)
# 2. Cadre de travail
Voici le Framework IRIS.

Les composants à l'intérieur d'IRIS représentent une production. Les adaptateurs entrants et les adaptateurs sortants nous permettent d'utiliser différents types de formats comme entrée et sortie pour notre base de données. Les applications composites nous donneront accès à la production via des applications externes comme les services REST.
Les flèches entre tous ces composants sont des **messages**. Ils peuvent être des demandes ou des réponses.
# 3. Adapter le framework
Dans notre cas, nous allons lire des lignes dans un fichier csv et les enregistrer dans la base de données IRIS.
Nous ajouterons ensuite une opération qui nous permettra de sauvegarder également des objets dans une base de données externe, en utilisant JDBC. Cette base de données sera située dans un conteneur docker, en utilisant postgre.
Enfin, nous verrons comment utiliser des applications composites pour insérer de nouveaux objets dans notre base de données ou pour consulter cette base (dans notre cas, via un service REST).
Le framework adapté à notre objectif nous donne :

# 4. Prérequis
Pour cette formation, vous aurez besoin de :
* VSCode : https://code.visualstudio.com/
* La suite d'addons InterSystems pour vscode : https://intersystems-community.github.io/vscode-objectscript/installation/
* Docker : https://docs.docker.com/get-docker/
* L'addon docker pour VSCode.
# 5. Mise en place
## 5.1. Conteneurs Docker
Afin d'avoir accès aux images InterSystems, nous devons nous rendre à l'url suivante : http://container.intersystems.com. Après nous être connectés avec nos identifiants InterSystems, nous obtiendrons notre mot de passe pour nous connecter au registre. Dans l'addon docker VScode, dans l'onglet image, en appuyant sur connect registry et en entrant la même url que précédemment (http://container.intersystems.com) comme registre générique, il nous sera demandé de donner nos identifiants. L'identifiant est l'identifiant habituel mais le mot de passe est celui que nous avons obtenu sur le site Web.
De là, nous devrions pouvoir construire et composer nos conteneurs (avec les fichiers `docker-compose.yml` et `Dockerfile` donnés).
## 5.2. Portail de gestion
Nous allons ouvrir un portail de gestion. Il nous donnera accès à une page web où nous pourrons créer notre production. Le portail devrait être situé à l'url : http://localhost:52775/csp/sys/UtilHome.csp?$NAMESPACE=IRISAPP. Vous aurez besoin des informations d'identification suivantes :
> IDENTIFIANT : SuperUser
>
> MOT DE PASSE : SYS
## 5.3. Sauvegarde de la progression
Une partie des choses que nous allons faire sera sauvegardée localement, mais tous les processus et productions sont sauvegardés dans le conteneur docker. Afin de persister tout notre progrès, nous devons exporter chaque classe qui est créée par le portail de gestion avec l'addon InterSystems `ObjectScript` :

Nous devrons sauvegarder notre production, le plan d'enregistrement, les processus métier et le transfert de données de cette manière. Après cela, lorsque nous fermerons notre conteneur docker et le recomposerons, nous aurons toujours toute notre progression sauvegardée localement (c'est, bien sûr, à faire après chaque changement via le portail). Pour le rendre à nouveau accessible à IRIS, nous devons compiler les fichiers exportés (en les sauvegardant, les addons InterSystems s'occupent du reste).
# 6. Productions
Nous pouvons maintenant créer notre première production. Pour cela, nous allons passer par les menus [Interoperability] et [Configurer] :

Nous devons ensuite appuyer sur [Nouveau], sélectionner le paquet [Formation] et choisir un nom pour notre production :

Immédiatement après avoir créé notre production, nous devons cliquer sur \[Paramètres de production] juste au-dessus de la section [Opérations]. Dans le menu latéral de droite, nous devrons activer [Test activé] dans la partie [Développement et débogage] de l'onglet [Paramètres\] (n'oubliez pas de cliquer sur [Appliquer]).

Dans cette première production, nous allons maintenant ajouter des opérations commerciales.
# 7. Opérations
Une Business Operation (BO) est une opération spécifique qui nous permettra d'envoyer des requêtes depuis IRIS vers une application / un système externe. Elle peut également être utilisée pour enregistrer directement dans IRIS ce que nous voulons.
Nous allons créer ces opérations en local, c'est-à-dire dans le fichier `Formation/BO/`. En sauvegardant les fichiers, nous les compilerons dans IRIS.
Pour notre première opération, nous allons sauvegarder le contenu d'un message dans la base de données locale.
Nous devons d'abord avoir un moyen de stocker ce message.
## 7.1. Création de notre classe de stockage
Les classes de stockage dans IRIS étendent le type `%Persistent`. Elles seront enregistrées dans la base de données interne.
Dans notre fichier `Formation/Table/Formation.cls` nous avons :
```objectscript
Class Formation.Table.Formation Extends %Persistent
{
Property Name As %String;
Property Salle As %String;
}
```
Notez que lors de l'enregistrement, des lignes supplémentaires sont automatiquement ajoutées au fichier. Elles sont obligatoires et sont ajoutées par les addons InterSystems.
## 7.2. Création de notre classe de messages
Ce message contient un objet `Formation`, situé dans le fichier `Formation/Obj/Formation.cls` :
```objectscript
Class Formation.Obj.Formation Extends (%SerialObject, %XML.Adaptor)
{
Property Nom As %String;
Property Salle As %String;
}
```
La classe `Message` utilisera cet objet `Formation`, `src/Formation/Msg/FormationInsertRequest.cls` :
```objectscript
Class Formation.Msg.FormationInsertRequest Extends Ens.Request
{
Property Formation As Formation.Obj.Formation;
}
```
## 7.3. Création de notre opération
Maintenant que nous avons tous les éléments dont nous avons besoin, nous pouvons créer notre opération, dans le fichier `Formation/BO/LocalBDD.cls` :
```objectscript
Class Formation.BO.LocalBDD Extends Ens.BusinessOperation
{
Parameter INVOCATION = "Queue";
Method InsertLocalBDD(pRequest As Formation.Msg.FormationInsertRequest, Output pResponse As Ens.StringResponse) As %Status
{
set tStatus = $$$OK
try{
set pResponse = ##class(Ens.Response).%New()
set tFormation = ##class(Formation.Table.Formation).%New()
set tFormation.Name = pRequest.Formation.Nom
set tFormation.Salle = pRequest.Formation.Salle
$$$ThrowOnError(tFormation.%Save())
}
catch exp
{
Set tStatus = exp.AsStatus()
}
Quit tStatus
}
XData MessageMap
{
InsertLocalBDD
}
}
```
La MessageMap nous donne la méthode à lancer en fonction du type de demande (le message envoyé à l'opération).
Comme nous pouvons le voir, si l'opération a reçu un message du type `Formation.Msg.FormationInsertRequest`, la méthode `InsertLocalBDD` sera appelée. Cette méthode enregistrera le message dans la base de données locale d'IRIS.
## 7.4. Ajout de l'opération à la production
Nous devons maintenant ajouter cette opération à la production. Pour cela, nous utilisons le portail de gestion. En appuyant sur le signe [+] à côté de [Opérations], nous avons accès à l'[Assistant d'opérations commerciales]. Là, nous choisissons la classe d'opération que nous venons de créer dans le menu déroulant.

## 9.3. Test
Un double clic sur l'opération nous permettra de l'activer. Après cela, en sélectionnant l'opération et en allant dans les onglets [Actions] dans le menu latéral droit, nous devrions être en mesure de tester l'opération (si ce n'est pas le cas, consultez la partie création de la production pour activer les tests / vous devrez peut-être démarrer la production si elle est arrêtée).
En faisant cela, nous allons envoyer à l'opération un message du type que nous avons déclaré plus tôt. Si tout se passe bien, les résultats devraient être comme indiqué ci-dessous :

L'affichage de la trace visuelle nous permettra de voir ce qui s'est passé entre les processus, les services et les opérations. Ici, nous pouvons voir le message envoyé à l'opération par le processus, et l'opération renvoyant une réponse (qui est juste une chaîne vide).
# 8. Processus métier
Les processus métier (BP) sont la logique métier de notre production. Ils sont utilisés pour traiter les demandes ou relayer ces demandes à d'autres composants de la production.
Les processus métier sont créés dans le portail de gestion :

## 8.1. BP simple
### 8.1.1. Création du processus
Nous sommes maintenant dans le concepteur de processus métier. Nous allons créer un BP simple qui appellera notre opération :

### 8.1.2. Modifier le contexte d'un BP
Un BP possède un **Contexte**. Il est composé d'une classe de requête, la classe de l'entrée, et d'une classe de réponse, la classe de la sortie. **Les processus métier n'ont qu'une entrée et une sortie**. Il est également possible d'ajouter des propriétés.
Puisque notre BP ne sera utilisé que pour appeler notre BO, nous pouvons mettre comme classe de requête la classe de message que nous avons créée (nous n'avons pas besoin d'une sortie puisque nous voulons juste insérer dans la base de données).

Nous avons ensuite choisi la cible de la fonction d'appel : notre BO. Cette opération, étant **appelée** a une propriété **callrequest**. Nous devons lier cette callrequest à la requête de la BP (elles sont toutes deux de la classe `Formation.Msg.FormationInsertRequest`), nous faisons cela en cliquant sur la fonction d'appel et en utilisant le constructeur de requête :

Nous pouvons maintenant sauvegarder cette BP (dans le package « Formation.BP » et sous le nom « InsertLocalBDD » ou « Main », par exemple). Tout comme les opérations, les processus peuvent être instanciés et testés via la configuration de production, pour cela ils doivent être compilés au préalable (sur l'écran Concepteur de processus métier).
Pour l'instant, notre processus ne fait que transmettre le message à notre opération. Nous allons le complexifier pour que le BP prenne en entrée une ligne d'un fichier CSV.
## 8.2. Lecture de lignes CSV par un BP
### 8.2.1. Création d'une carte d'enregistrement
Afin de lire un fichier et de mettre son contenu dans un fichier, nous avons besoin d'une Carte d'enregistrement (RM). Il existe un Record Mapper spécialisé pour les fichiers CSV dans le menu [Interoperability > Build] du portail de gestion :

Nous créerons le mapper comme suit :

Nous allons créer le mappeur comme ceci :

Maintenant que la carte est créée, nous devons la générer (avec le bouton Générer). Nous devons maintenant avoir une Transformation de données à partir du format de la carte des enregistrements et un message d'insertion.
### 8.2.2. Création d'une transformation de données
Nous trouverons le créateur de transformations de données (DT) dans le menu [Interoperability > Builder]. Nous allons créer notre DT comme ceci (si vous ne trouvez pas `Formation.RM.Csv.Record`, peut-être que vous n'avez pas généré la carte d'enregistrement) :

Nous pouvons pouvons maintenant mapper les différents champs ensemble :

### 8.2.3. Ajout de la transformation des données au processus métier
La première chose que nous devons changer est la classe de requête de la BP, puisque nous devons avoir en entrée la carte d'enregistrement que nous avons créée.

Nous pouvons alors ajouter notre transformation (le nom du processus ne change rien, d'ici nous avons choisi de le nommer `Main`) :

L'activité de transformation va prendre la requête de la BP (un enregistrement du fichier CSV, grâce à notre Record Mapper), et la transformer en un message `FormationInsertRequest`. Afin de stocker ce message pour l'envoyer à la BO, nous devons ajouter une propriété au contexte de la BP.

Nous pouvons maintenant configurer notre fonction de transformation pour qu'elle prenne l'entrée du BP et enregistre sa sortie dans la propriété nouvellement créée. La source et la cible de la transformation `RmToMsg` sont respectivement `request` et `context.Msg` :

Nous devons faire de même pour `Call BO`. Son entrée, ou `callrequest`, est la valeur stockée dans `context.msg` :

Au final, le flux dans la BP peut être représenté comme ceci :

### 8.2.4. Configuration de la production
Avec le signe [+], nous pouvons ajouter notre nouveau processus à la production (si ce n'est pas déjà fait). Nous avons également besoin d'un service générique pour utiliser le record map, nous utilisons `EnsLib.RecordMap.Service.FileService` (nous l'ajoutons avec le bouton [+] à côté des services). Nous paramétrons ensuite ce service :

Nous devrions maintenant être en mesure de tester notre BP.
### 8.2.5. Test
Nous testons l'ensemble de la production comme suit :

Dans le menu `Explorateur système > SQL`, vous pouvez exécuter la commande
````sql
SELECT
ID, Name, Salle
FROM Formation_Table.Formation
````
pour voir les objets que nous venons d'enregistrer.
# 9. Accéder à une base de données externe à l'aide de JDBC
Dans cette section, nous allons créer une opération pour sauvegarder nos objets dans une base de données externe. Nous utiliserons l'API JDBC, ainsi que l'autre conteneur docker que nous avons mis en place, avec postgre dessus.
## 9.1. Création de notre nouvelle opération
Notre nouvelle opération, dans le fichier `Formation/BO/RemoteBDD.cls` est la suivante :
````objectscript
Include EnsSQLTypes
Class Formation.BO.RemoteBDD Extends Ens.BusinessOperation
{
Parameter ADAPTER = "EnsLib.SQL.OutboundAdapter";
Property Adapter As EnsLib.SQL.OutboundAdapter;
Parameter INVOCATION = "Queue";
Method InsertRemoteBDD(pRequest As Formation.Msg.FormationInsertRequest, Output pResponse As Ens.StringResponse) As %Status
{
set tStatus = $$$OK
try{
set pResponse = ##class(Ens.Response).%New()
set ^inc = $I(^inc)
set tInsertSql = "INSERT INTO public.formation (id, nom, salle) VALUES(?, ?, ?)"
$$$ThrowOnError(..Adapter.ExecuteUpdate(.nrows,tInsertSql,^inc,pRequest.Formation.Nom, pRequest.Formation.Salle ))
}
catch exp
{
Set tStatus = exp.AsStatus()
}
Quit tStatus
}
XData MessageMap
{
InsertRemoteBDD
}
}
````
Cette opération est similaire à la première que nous avons créée. Lorsqu'elle recevra un message du type `Formation.Msg.FormationInsertRequest`, elle utilisera un adaptateur pour exécuter des requêtes SQL. Ces requêtes seront envoyées à notre base de données postgre.
## 9.2. Configurer la production
Maintenant, via le portail de gestion, nous allons instancier cette opération (en l'ajoutant avec le signe [+] dans la production).
Nous devrons également ajouter le JavaGateway pour le pilote JDBC dans les services. Le nom complet de ce service est `EnsLib.JavaGateway.Service`.

Nous devons maintenant configurer notre opération. Puisque nous avons mis en place un conteneur postgre, et connecté son port `5432`, les valeurs dont nous avons besoin dans les paramètres suivants sont :
> DSN : `jdbc:postgresql://db:5432/DemoData`
>
> Pilote JDBC : `org.postgresql.Driver`
>
> JDBC Classpath : `/tmp/iris/postgresql-42.2.14.jar`

Enfin, nous devons configurer les informations d'identification pour avoir accès à la base de données distante. Pour cela, nous devons ouvrir la visionneuse d'identifiants :

L'identifiant et le mot de passe sont tous deux `DemoData`, comme nous l'avons configuré dans le fichier `docker-compose.yml`.

De retour à la production, nous pouvons ajouter `« Postgre »` dans le champ [Identifiant] dans les paramètres de notre opération (il devrait être dans le menu déroulant). Avant de pouvoir le tester, nous devons ajouter le JGService à l'opération. Dans l'onglet [Paramètres], dans les [Paramètres supplémentaires] :

## 7.5. Test
Lors du test, la trace visuelle doit indiquer un succès :

Lors du test, la trace visuelle doit montrer un succès :
## 9.4. Exercice
À titre d'exercice, il pourrait être intéressant de modifier BO.LocalBDD afin qu'il renvoie un booléen qui indiquera au BP d'appeler BO.RemoteBDD en fonction de la valeur de ce booléen.
**Indice** : cela peut être fait en changeant le type de réponse que LocalBDD renvoie et en ajoutant une nouvelle propriété au contexte et en utilisant l'activité `if` dans notre BP.
## 9.5. Solution
Tout d'abord, nous devons avoir une réponse de notre opération LocalBDD. Nous allons créer un nouveau message, dans le fichier `Formation/Msg/FormationInsertResponse.cls` :
````objectscript
Class Formation.Msg.FormationInsertResponse Extends Ens.Response
{
Property Double As %Boolean;
}
````
Ensuite, nous modifions la réponse de LocalBDD par cette réponse, et définissons la valeur de son booléen de manière aléatoire (ou non) :
````objectscript
Method InsertLocalBDD(pRequest As Formation.Msg.FormationInsertRequest, Output pResponse As Formation.Msg.FormationInsertResponse) As %Status
{
set tStatus = $$$OK
try{
set pResponse = ##class(Formation.Msg.FormationInsertResponse).%New()
if $RANDOM(10) < 5 {
set pResponse.Double = 1
}
else {
set pResponse.Double = 0
}
...
````
Nous allons maintenant créer un nouveau processus (copié sur celui que nous avons fait), où nous ajouterons une nouvelle propriété de contexte, de type `%Boolean` :

Cette propriété sera remplie avec la valeur du callresponse.Double de notre appel d'opération (nous devons définir la [Classe de message de réponse] à notre nouvelle classe de message) :

Nous ajoutons ensuite une activité `if`, avec la propriété `context.Double` comme condition :

TRÈS IMPORTANT : nous devons décocher **Asynchrone** dans les paramètres de notre appel LocallBDD, ou l'activité if se déclenchera avant de recevoir la réponse booléenne.
Enfin, nous configurons notre activité d'appel avec comme cible le BO RemoteBDD :

Pour compléter l'activité if, nous devons faire glisser un autre connecteur de la sortie du `if` vers le triangle `join` ci-dessous. Comme nous ne ferons rien si le booléen est faux, nous laisserons ce connecteur vide. Après avoir compilé et instancié, nous devrions être en mesure de tester notre nouveau processus. Pour cela, nous devons changer le `Target Config Name` de notre File Service.
Dans la trace, nous devrions avoir environ la moitié des objets lus dans le csv enregistrés également dans la base de données distante.
# 10. Service REST
Dans cette partie, nous allons créer et utiliser un service REST.
## 10.1. Créer le service
Pour créer un service REST, nous avons besoin d'une classe qui étend %CSP.REST, dans `Formation/REST/Dispatch.cls` nous avons :
````objectscript
Class Formation.REST.Dispatch Extends %CSP.REST
{
/// Ignore any writes done directly by the REST method.
Parameter IgnoreWrites = 0;
/// Convertir par défaut le flux d'entrée en Unicode
Parameter CONVERTINPUTSTREAM = 1;
/// Le jeu de caractères par défaut est utf-8
Parameter CHARSET = "utf-8";
Parameter HandleCorsRequest = 1;
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
}
/// Obtenir cette spécification
ClassMethod import() As %Status
{
set tSc = $$$OK
Try {
set tBsName = "Formation.BS.RestInput"
set tMsg = ##class(Formation.Msg.FormationInsertRequest).%New()
set body = $zcvt(%request.Content.Read(),"I","UTF8")
set dyna = {}.%FromJSON(body)
set tFormation = ##class(Formation.Obj.Formation).%New()
set tFormation.Nom = dyna.nom
set tFormation.Salle = dyna.salle
set tMsg.Formation = tFormation
$$$ThrowOnError(##class(Ens.Director).CreateBusinessService(tBsName,.tService))
$$$ThrowOnError(tService.ProcessInput(tMsg,.output))
} Catch ex {
set tSc = ex.AsStatus()
}
Quit tSc
}
}
````
Cette classe contient une route pour importer un objet, liée au verbe POST :
````xml
````
La méthode d'importation va créer un message qui sera envoyé à un service métier.
## 10.2. Ajouter notre BS
Nous allons créer une classe générique qui va router toutes ses sollicitations vers `TargetConfigNames`. Cette cible sera configurée lorsque nous instancierons ce service. Dans le fichier `Formation/BS/RestInput.cls` nous avons :
```objectscript
Class Formation.BS.RestInput Extends Ens.BusinessService
{
Property TargetConfigNames As %String(MAXLEN = 1000) [ InitialExpression = "BuisnessProcess" ];
Parameter SETTINGS = "TargetConfigNames:Basic:selector?multiSelect=1&context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}";
Method OnProcessInput(pDocIn As %RegisteredObject, Output pDocOut As %RegisteredObject) As %Status
{
set status = $$$OK
try {
for iTarget=1:1:$L(..TargetConfigNames, ",") {
set tOneTarget=$ZStrip($P(..TargetConfigNames,",",iTarget),"W") Continue:""=tOneTarget
$$$ThrowOnError(..SendRequestSync(tOneTarget,pDocIn,.pDocOut))
}
} catch ex {
set status = ex.AsStatus()
}
Quit status
}
}
```
De retour à la configuration de production, nous ajoutons le service de la manière habituelle. Dans le champ [Target Config Names], nous mettons notre BO LocalBDD :

Pour utiliser ce service, nous devons le publier. Pour cela, nous utilisons le menu [Modifier l'application Web] :

## 10.3. Test
Enfin, nous pouvons tester notre service avec n'importe quel type de client REST :

# Conclusion
Grâce à cette formation, nous avons créé une production capable de lire les lignes d'un fichier csv et de sauvegarder les données lues à la fois dans la base de données IRIS et dans une base de données externe en utilisant JDBC. Nous avons également ajouté un service REST afin d'utiliser le verbe POST pour sauvegarder de nouveaux objets.
Nous avons découvert les principaux éléments du cadre d'interopérabilité d'InterSystems.
Nous l'avons fait en utilisant docker, vscode et le portail de gestion IRIS d'InterSystems.
Article
Lorenzo Scalese · Juin 13, 2022
Une session concurrente dans IRIS : SQL, Objects, REST, et GraphQL
Kazimir Malevitch, "Athlètes" (1932)
"Mais bien sûr, vous ne comprenez pas ! Comment celui qui a toujours voyagé en calèche peut-il comprendre les sentiments et les impressions du voyageur en express ou du pilote dans les airs ?"
Kazimir Malevich (1916)
Introduction
Nous avons déjà abordé le sujet des raisons pour lesquelles la représentation objet/type est préférable à SQL pour la mise en œuvre des modèles de domaine. Et ces conclusions et ces faits sont aussi vrais aujourd'hui qu'ils l'ont toujours été. Alors pourquoi devrions-nous faire un pas en arrière et discuter des technologies qui ramènent les abstractions au niveau global, où elles se trouvaient à l'ère pré-objet et pré-type ? Et pourquoi devrions-nous encourager l'utilisation d'un code spaghetti, qui donne lieu à des bogues difficiles à repérer et qui ne repose que sur les compétences virtuoses des développeurs ?
Plusieurs arguments sont favorables à la transmission de données via des API basées sur SQL/REST/GraphQL plutôt qu'à leur représentation sous forme de types/objects:
Ces technologies sont très bien étudiées et assez faciles à appliquer.
Ils jouissent d'une incroyable popularité et ont été largement mis en œuvre dans des logiciels accessibles et à code source ouvert.
Vous n'avez souvent pas d'autre choix que d'utiliser ces technologies, notamment sur le web et dans les bases de données.
Plus important encore, les API utilisent toujours des objets, car ils constituent le moyen le plus approprié d'implémenter les API dans le code.
Avant de parler de la mise en œuvre des API, jetons d'abord un coup d'œil aux couches d'abstraction sous-jacentes. Le schéma ci-dessous montre comment les données se déplacent entre l'endroit où elles sont stockées de façon permanente et l'endroit où elles sont traitées et présentées à l'utilisateur de nos applications.
Aujourd'hui, les données sont stockées soit sur des disques durs rotatifs (HDD), soit, pour utiliser une technologie plus moderne, dans des puces de mémoire flash utilisées dans les disques SSD. Les données sont écrites et lues à l'aide d'un flux constitué de blocs de stockage distincts sur le disque dur/le disque SSD.
La division en blocs n'est pas aléatoire. Elle est plutôt déterminée par la physique, la mécanique et l'électronique du support de stockage des données. Dans un disque dur, il s'agit des pistes/secteurs d'un disque magnétique rotatif. Dans un SSD, il s'agit des segments de mémoire dans une puce de silicium réinscriptible.
Le principe est le même : il s'agit de blocs d'informations que nous devons trouver et assembler afin de récupérer les éléments de données dont nous avons besoin. Les données doivent être assemblées dans des structures qui correspondent à notre modèle/type de données avec les valeurs qui correspondent au moment de la requête. Le DBMS, associé au sous-système de fichiers du système d'exploitation, est responsable du processus d'assemblage et de récupération des données.
Nous pouvons contourner le DBMS en nous adressant directement au système de fichiers ou même au disque dur/SSD. Mais dans ce cas, nous renonçons à deux ponts super importants vers les données : entre les blocs de stockage et les flux de fichiers, et entre les fichiers et la structure ordonnée dans le modèle de base de données. En d'autres termes, nous assumons la responsabilité du développement de l'ensemble du code pour le traitement des blocs, des fichiers et des modèles, y compris toutes les optimisations, le débogage minutieux et les tests de fiabilité à long terme.Le DBMS nous offre une excellente opportunité de traiter les données dans un langage de haut niveau, en utilisant des modèles et des représentations compréhensibles. C'est l'un des grands avantages de ces systèmes. Les DBMS et les plates-formes de données, comme InterSystems IRIS, offrent encore plus : la possibilité d'accéder simultanément à des données ordonnées de manières très diverses. Et c'est à vous de choisir lequel utiliser dans votre projet.
Profitons de la variété d'outils qu'IRIS nous offre. Rendons le code plus attrayant et plus clair. Nous utiliserons directement le langage orienté objet ObjectScript pour utiliser et développer les API. En d'autres termes, par exemple, nous appellerons le code SQL directement depuis l'intérieur du logiciel ObjectScript. Pour les autres APIs, nous utiliserons des bibliothèques prêtes à l'emploi et des outils Embedded ObjectScript.
Nous prendrons nos exemples dans le projet SQLZoo Internet, qui offre des ressources d'apprentissage pour SQL. Nous utiliserons les mêmes données dans nos autres exemples d'API.
Si vous souhaitez avoir une vue d'ensemble de la variété des approches en matière de conception d'API et profiter de solutions toutes faites, voici une collection intéressante et utile d'API publiques, qui ont été rassemblées en un seul projet sur GitHub.
SQL
Il n'y a pas de moyen plus naturel de commencer qu'avec SQL. Qui n'est pas familier avec ce langage ?
TIl existe un grand nombre de tutoriels et de livres sur SQL. Nous nous baserons sur SQLZoo. Il s'agit d'un bon cours de SQL pour débutants, avec des exemples, des instructions et une référence du langage.
Transférons certaines tâches de SQLZoo vers la plateforme IRIS et résolvons-les en utilisant diverses méthodes.
En combien de temps pouvez-vous accéder à InterSystems IRIS sur votre ordinateur ? L'une des options les plus rapides consiste à déployer un conteneur dans Docker à partir d'une image InterSystems IRIS Community Edition prête à l'emploi. InterSystems IRIS Community Edition est une version gratuite pour les développeurs de la plate-forme de données InterSystems IRIS Data Platform.Autres moyens d'accéder à InterSystems IRIS Community Edition dans le portail de formation
Comment déplacer les données de SQLZoo vers notre propre stockage d'instance IRIS.
Pour ce faire :
Ouvrez le Management Portal,
Passez à la zone USER - dans Namespace %SYS, cliquez sur le lien "Switch" et sélectionnez USER
Basculer vers Système > SQL - ouvrez System Explorer, puis SQL et cliquez sur le bouton "Go".
Sur le côté droit l'onglet "Execute query" avec le bouton "Execute" est ouvert - c'est ce dont nous avons besoin.
Pour en savoir plus sur la façon de travailler avec SQL via le Management Portal, consultez la documentation.
Consultez les scripts prêts à l'emploi pour déployer la base de données et les données de test SQLZoo dans la description de la section Données.
Voici quelques liens directs pour la table World:
Un script qui est utilisé pour créer la base de données World
Les données contenues dans cette table
Le script de création de la base de données peut être exécuté dans le formulaire Query Executor du Management Portal IRIS.
CREATE TABLE world(
name VARCHAR(50) NOT NULL
,continent VARCHAR(60)
,area DECIMAL(10)
,population DECIMAL(11)
,gdp DECIMAL(14)
,capital VARCHAR(60)
,tld VARCHAR(5)
,flag VARCHAR(255)
,PRIMARY KEY (name)
)
Pour charger les données de test pour le formulaire Query Executor basculez vers le menu Wizards > Data Import. Notez que le répertoire contenant le fichier de données de test doit être ajouté à l'avance, lorsque vous créez le conteneur, ou chargé depuis votre ordinateur via le navigateur. Cette option est disponible dans le panneau de contrôle de l'assistant d'importation de données.
Vérifiez si le tableau contenant les données est présent en exécutant ce script dans le formulaire Query Executor form:
SELECT * FROM world
Nous pouvons maintenant accéder aux exemples et aux tâches à partir du site Web de SQLZoo. Tous les exemples ci-dessous exigent que vous implémentiez une requête SQL dans la première attribution :
SELECT population FROM world WHERE name = 'France'
Ainsi, vous pourrez continuer à travailler de manière transparente avec l'API en transférant les tâches de SQLZoo vers la plateforme IRIS.
Attention : comme je l'ai découvert, les données de l'interface du site SQLZoo sont différentes des données exportées. Au moins dans le premier exemple, les valeurs de la population de la France et de l'Allemagne sont différentes. Ne vous en préoccupez pas. Utilisez les données d'Eurostat comme référence.
Un autre moyen pratique d'obtenir un accès SQL à la base de données dans IRIS est l'éditeur Visual Studio Code avec le plugin SQLTools et le pilote SQLTools pour InterSystems IRIS. Cette solution est populaire auprès des développeurs - essayez-la.
Afin de passer sans encombre à l'étape suivante et d'obtenir un accès objet à notre base de données, faisons un petit détour pour passer des requêtes SQL "pures" aux requêtes Embedded SQL au code de l'application en ObjectScript, qui est un langage orienté objet intégré à IRIS.
Comment configurer l'accès à IRIS et développer en ObjectScript dans VSCode.
Class User.worldquery
{
ClassMethod WhereName(name As %String)
{
&sql(
SELECT population INTO :population
FROM world
WHERE name = :name
)
IF SQLCODE<0 {WRITE "SQLCODE error ",SQLCODE," ",%msg QUIT}
ELSEIF SQLCODE=100 {WRITE "Query returns no results" QUIT}
WRITE name, " ", population
}
}
Vérifions le résultat dans le terminal :
do ##class(User.worldquery).WhereName("France")
Vous devriez recevoir comme réponse le nom du pays et le nombre d'habitants.
Objets/Types
Passons maintenant à l'histoire de REST/GraphQL. Nous mettons en œuvre une API pour des protocoles web. Le plus souvent, nous aurons du code source à disposition côté serveur dans un langage qui supporte bien les types, voire un paradigme entièrement orienté objet. Voici quelques-uns des langages dont nous parlons : Spring en Java/Kotlin, Django en Python, Ruby on Rails, ASP.NET en C# ou Angular en TypeScript. Et, bien entendu, des objets en ObjectScript, qui est natif de la plateforme IRIS.
Pourquoi est-ce important ? Les types et les objets de votre code seront simplifiés en structures de données lorsqu'ils seront envoyés. Vous devez tenir compte de la manière dont les modèles sont simplifiés dans le programme, ce qui revient à prendre en compte les pertes dans les modèles relationnels. Vous devez également vous assurer que, de l'autre côté de l'API, les modèles sont correctement restaurés et peuvent être utilisés sans aucune distorsion. Cela représente une charge supplémentaire : une responsabilité supplémentaire pour vous en tant que programmeur. En dehors du code et au-delà de l'aide des traducteurs, compilateurs et autres outils automatiques, vous devez vous assurer en permanence que les modèles sont correctement transférés.
Si nous examinons la question ci-dessus sous un autre angle, nous ne voyons pas encore de technologies et d'outils à l'horizon qui puissent être utilisés pour transférer facilement des types/objets d'un programme dans un langage à un programme dans un autre. Que reste-t-il ? Il existe des implémentations simplifiées de SQL/REST/GraphQL, et une multitude de documents décrivant l'API dans un langage convivial. La documentation informelle (du point de vue de l'ordinateur) destinée aux développeurs décrit exactement ce qui doit être traduit en code formel en utilisant tous les moyens disponibles, afin que l'ordinateur puisse le traiter.
Les programmeurs développent constamment différentes approches pour résoudre les problèmes mentionnés ci-dessus. L'une de ces approches réussies est le paradigme inter-langues dans le DBMS objet de la plate-forme IRIS.
Le tableau suivant devrait vous aider à comprendre la relation entre les modèles OPP et SQL dans IRIS :
Programmation orientée objet (OOP)
Langage de requête structuré (SQL)
Package
Schéma
Classe
Tableau
Propriété
Colonne
Méthode
Procédure stockée
Relation entre deux classes
Contrainte de clé étrangère, jointure intégrée
Objet (en mémoire ou sur le disque)
Ligne (sur le disque)
Pour en savoir plus sur l'affichage des modèles objet et relationnel, consultez la documentation d'IRIS.
Lors de l'exécution de notre requête SQL pour créer la table world de l'exemple ci-dessus, IRIS générera automatiquement les descriptions de l'objet correspondant dans la classe nommée User.world.
Class User.world Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {_SYSTEM}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = world ]
{
Property name As %Library.String(MAXLEN = 50) [ Required, SqlColumnNumber = 2 ];
Property continent As %Library.String(MAXLEN = 60) [ SqlColumnNumber = 3 ];
Property area As %Library.Numeric(MAXVAL = 9999999999, MINVAL = -9999999999, SCALE = 0) [ SqlColumnNumber = 4 ];
Property population As %Library.Numeric(MAXVAL = 99999999999, MINVAL = -99999999999, SCALE = 0) [ SqlColumnNumber = 5 ];
Property gdp As %Library.Numeric(MAXVAL = 99999999999999, MINVAL = -99999999999999, SCALE = 0) [ SqlColumnNumber = 6 ];
Property capital As %Library.String(MAXLEN = 60) [ SqlColumnNumber = 7 ];
Property tld As %Library.String(MAXLEN = 5) [ SqlColumnNumber = 8 ];
Property flag As %Library.String(MAXLEN = 255) [ SqlColumnNumber = 9 ];
Parameter USEEXTENTSET = 1;
/// Bitmap Extent Index auto-generated by DDL CREATE TABLE statement. Do not edit the SqlName of this index.
Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ];
/// DDL Primary Key Specification
Index WORLDPKey2 On name [ PrimaryKey, Type = index, Unique ];
}
Il s'agit d'un modèle que vous pouvez utiliser pour développer votre application dans un style orienté objet. Tout ce que vous avez à faire est d'ajouter des méthodes à la classe en ObjectScript, qui dispose de bundles prêts à l'emploi pour la base de données. En fait, les méthodes de cette classe sont des "procédures stockées", pour reprendre la terminologie SQL.
Essayons d'implémenter le même exemple que nous avons réalisé précédemment en utilisant SQL. Ajoutez la méthode WhereName à la User.world classe, qui jouera le rôle de concepteur de l'objet "Informations sur le pays" pour le nom de pays saisi :
ClassMethod WhereName(name As %String) As User.world
{
Set id = 1
While ( ..%ExistsId(id) ) {
Set countryInfo = ..%OpenId(id)
if ( countryInfo.name = name ) { Return countryInfo }
Set id = id + 1
}
Return countryInfo = ""
}
Vérifiez les éléments suivants dans le terminal :
set countryInfo = ##class(User.world).WhereName("France")
write countryInfo.name
write countryInfo.population
Nous pouvons voir dans cet exemple que, pour trouver l'objet souhaité par le nom du pays, contrairement à une requête SQL, nous devons trier manuellement les enregistrements de la base de données un par un. Au pire, si notre objet se trouve à la fin de la liste (ou n'y figure pas du tout), nous devrons trier tous les enregistrements. Il existe une discussion séparée sur la façon dont vous pouvez accélérer le processus de recherche en indexant les champs d'objet et en générant automatiquement les méthodes de classe dans IRIS. Vous pouvez en savoir plus dans la documentation et dans les articles du portail de la communauté des développeurs.
Par exemple, pour notre classe, connaissant le nom de l'index généré par IRIS à partir du nom du pays WORLDPKey2 vous pouvez initialiser/concevoir un objet à partir de la base de données en utilisant une seule requête rapide :
set countryInfo = ##class(User.world).WORLDPKey2Open("France")
Vérifiez également :
write countryInfo.name
write countryInfo.population
Vous trouverez des lignes directrices pour décider si vous devez utiliser l'accès objet ou SQL pour les objets stockés dans cette documentation.
Bien entendu, vous devez toujours garder à l'esprit que vous ne pouvez utiliser pleinement qu'un seul d'entre eux pour vos tâches.
En outre, grâce à la disponibilité de paquets binaires prêts à l'emploi dans IRIS qui prennent en charge les langages de programmation orientée objet les plus courants, tels que Java, Python, C, C# (.Net), JavaScript et même Julia (voir GitHub et OpenExchange), qui gagne rapidement en popularité , vous pourrez toujours choisir les outils de développement du langage qui vous conviennent le mieux.
Plongeons maintenant dans la discussion sur les données dans l'API Web.
API Web REST, ou RESTful
Sortons des limites du serveur et du terminal familier et utilisons des interfaces plus courantes : le navigateur et les applications similaires. Ces applications s'appuient sur les protocoles hypertextes de la famille HTTP pour gérer les interactions entre les systèmes. IRIS est livré avec une série d'outils adaptés à cette fin, notamment un véritable serveur de base de données et le serveur HTTP Apache.
Le transfert d'état représentationnel (REST) est un style architectural pour la conception d'applications distribuées et, en particulier, d'applications Web. Bien que populaire, REST n'est qu'un ensemble de principes architecturaux, tandis que SOAP est un protocole standard maintenu par le World Wide Web Consortium (W3C), et les technologies basées sur SOAP sont donc soutenues par une norme.
L'ID global dans REST est une URL, et il définit chaque unité d'information successive lors de l'échange avec une base de données ou une application back-end. Voir la documentation sur le développement de services REST dans IRIS.
Dans notre exemple, l'identifiant de base sera quelque chose comme la base de l'adresse du serveur IRIS, http://localhost:52773, et le /world/ chemin /world/ vers nos données qui en est un sous-répertoire. En particulier, nous parlons de notre répertoire de pays /world/France.
Il ressemblera à ce qui suit dans un conteneur Docker :
http://localhost:52773/world/France
Si vous développez une application complète, veillez à consulter les recommandations de la documentation IRIS. L'une d'entre elles est basée sur la description de l'API REST conformément à la spécification OpenAPI 2.0.
Faisons cela de la manière la plus simple, en implémentant l'API manuellement. Dans notre exemple, nous allons créer la solution REST la plus simple qui ne nécessite que deux étapes dans IRIS :
Créer un moniteur de chemin de classe dans l'URL, qui héritera de la classe système %CSP.REST
Ajoutez un appel à notre classe de surveillance lors de la configuration de l'application web IRIS
Étape 1 : Surveillance de la classe
La façon dont vous pouvez implémenter une classe devrait être claire. Suivez les instructions de la documentation pour créer un REST " à la main ".
/// Description
Class User.worldrest Extends %CSP.REST
{
Parameter UseSession As Integer = 1;
Parameter CHARSET = "utf-8";
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/:name" Method="GET" Call="countryInfo" />
</Routes>
}
}
Veillez à inclure une méthode de gestion. Elle doit exécuter exactement la même fonction que les appels dans le terminal de l'exemple précédent :
ClassMethod countryInfo(name As %String) As %Status
{
set countryInfo = ##class(User.world).WhereName(name)
write "Country: ", countryInfo.name
write "<br>"
write "Population: ", countryInfo.population
return $$$OK
}
Comme vous pouvez le constater, le paramètre qui commence par deux points, :name, est indiqué pour transmettre aux paramètres le nom de la méthode du gestionnaire appelé dans le moniteur à partir de la requête REST entrante.
Étape 2 : Configuration de l'application Web IRIS
Sous Administration du système > Sécurité > Applications > Applications Web,, ajoutez une nouvelle application Web avec une adresse URL d'entrée à /world /world et le gestionnaire suivant : our worldrest monitor class . Une fois qu'elle est configurée, l'application Web devrait répondre immédiatement lorsque vous basculez de http://localhost:52773/world/France. N'oubliez pas que la base de données est sensible à la casse. Il faut donc utiliser la casse correcte lors de la transmission des données de la requête au paramètre de la méthode.
Astuces :
Utilisez les outils de débogage si nécessaire. Vous trouverez une bonne description dans cet article en deux parties (consultez également les commentaires).
Si l'erreur "401 Unauthorized" apparaît, et que vous êtes sûr que la classe de moniteur est sur le serveur et qu'il n'y a pas d'erreurs dans le lien, essayez d'ajouter le rôle %All dans l'onglet Application Roles des paramètres de l'application Web. Il ne s'agit pas d'une méthode entièrement sécurisée, et vous devez comprendre les implications possibles de l'autorisation d'accès pour tous les rôles, mais elle est acceptable pour une installation locale.
GraphQL
Il s'agit d'un nouveau territoire dans le sens où vous ne trouverez rien dans la documentation actuelle d'IRIS sur les API utilisant GraphQL. Toutefois, cela ne doit pas nous empêcher d'utiliser ce merveilleux outil.
Cela ne fait que cinq ans que GraphQL est devenu public. Développé par la Fondation Linux, GraphQL est un langage d'interrogation pour les API. Et l'on peut sans doute affirmer qu'il s'agit de la meilleure technologie issue de l'amélioration de l'architecture REST et des différentes API web. Voici un court exposé introductif à ce sujet pour les débutants. Et, grâce aux efforts des passionnés et des ingénieurs d'InterSystems, IRIS offre un support pour GraphQL depuis 2018.
Voici l'article correspondant “Implémentation de GraphQL pour les plateformes InterSystems”. Et voici GraphQL compris, expliqué et implémenté.
L'application GraphQL se compose de deux modules : le back-end de l'application du côté d'IRIS et la partie front-end qui s'exécute dans le navigateur. En d'autres termes, vous devez la configurer conformément aux instructions relatives à l'application Web GraphQL et GraphiQL.
Par exemple, voici à quoi la configuration de l'application ressemble pour moi dans IRIS à l'intérieur d'un conteneur Docker. Il s'agit des paramètres d'une application Web GraphQL qui agit comme un moniteur REST et un gestionnaire de schéma de base de données : Et la seconde application GraphQL est une interface utilisateur pour le navigateur, écrite en HTML et JavaScript : Il peut être exécuté en basculant de http://localhost:52773/graphiql/index.html. Sans paramètres restrictifs supplémentaires, l'application récupère immédiatement tous les schémas de base de données qu'elle peut trouver dans la zone d'installation. Cela signifie que nos exemples vont commencer à fonctionner immédiatement. De plus, le front-end fournit un merveilleux tableau organisé d'indices à partir des objets disponibles.
Voici un exemple de requête GraphQL pour notre base de données :
{
User_world ( name: France ) {
name
population
}
}
Et voici la réponse correspondante :
{
"data": {
"User_world": [
{
"name": "France",
"population": 65906000
}
]
}
}
Voici à quoi cela ressemble dans le navigateur :
Résumé
Nom de la technologie
Âge de la technologie
Exemple de requête
SQL
50 ans Edgar F. Codd
SELECT population FROM world WHERE name = 'France'
OOP
40 ans Alan Kay and Dan Ingalls
set countryInfo = ##class(User.world).WhereName("France")
REST
20 ans Roy Thomas Fielding
http://localhost:52773/world/France
GraphQL
5 ans Lee Byron
{ User_world ( name: France ) { name population } }
Beaucoup d'énergie est investie dans des technologies telles que SQL, REST, et probablement aussi GraphQL. Elles ont également une longue histoire. Elles sont toutes compatibles les unes avec les autres, au sein de la plateforme IRIS, pour créer des programmes qui traitent les données.
Bien que cela n'ait pas été mentionné dans cet article, IRIS prend également en charge d'autres API, qui sont basées sur XML (SOAP) et JSON, et qui sont bien implémentées.
À moins que vous ne vous occupiez spécifiquement, par exemple, du marshaling de vos objets, n'oubliez pas que les données échangées via l'API représentent toujours une version incomplète et dépouillée d'un transfert d'objet. En tant que développeur (et non en tant que code), vous êtes responsable du transfert correct des informations relatives au type de données d'un objet .
Une question pour vous, chers lecteurs
L'objectif de cet article n'était pas seulement de comparer les API modernes, ni même de passer en revue les capacités de base d'IRIS. Il s'agissait de vous aider à voir à quel point il est facile de passer d'une API à l'autre pour accéder à une base de données, de faire vos premiers pas dans IRIS et d'obtenir rapidement des résultats de votre tâche. C'est pourquoi je serais très intéressé de savoir ce que vous en pensez :
Ce type d'approche vous aide-t-il à être opérationnel avec le logiciel ?
Quelles sont les étapes du processus qui rendent difficile la maîtrise des outils de travail avec l'API dans IRIS ?
Pouvez-vous citer un obstacle que vous n'auriez pas pu prévoir ?
Si vous connaissez un utilisateur qui apprend encore à utiliser IRIS, demandez-lui de laisser un commentaire ci-dessous. La discussion qui en résultera sera utile à toutes les personnes qui y participent.
Article
Lorenzo Scalese · Juil 11, 2022
**Historique**
| Version | Date | Changements |
| :----- | :-- | :----- |
| V1 | 2022-02-08 | Version initiale |
| V1.1 | 2022-04-06 | Génération de certificats avec le fichier sh au lieu de pki-scriptUtilisation de variables d'environnement dans des fichiers de configuration |
Salut, communauté,
Avez-vous déjà mis en place un environnement miroir ? Dispose-t-il d'un réseau privé, d'une adresse IP virtuelle et d'une configuration SSL ?
Après avoir fait cela plusieurs fois, je me suis rendu compte que c'est long, et qu'il y a beaucoup d'actions manuelles nécessaires pour générer des certificats et configurer chaque instance IRIS.
C'est une vraie casse-tête pour les personnes qui ont souvent à le faire.
Par exemple, une équipe d'assurance qualité peut avoir besoin de créer un nouvel environnement pour chaque nouvelle version d'application à tester. L'équipe de support peut avoir besoin de créer un environnement pour reproduire un problème complex.
Il faut absolument des outils pour les créer rapidement.
Dans cet article, nous allons créer un exemple pour configurer un miroir avec :
- Arbitre.
- Membre primaire.
- Membre de secours en cas de panne.
- Membre asynchrone de rapport en lecture-écriture.
- Configuration SSL pour les transferts de journaux entre les nœuds.
- Réseau privé pour le miroir.
- Adresse IP virtuelle.
- Une base de données en miroir.

À première vue, cela semble un peu complexe et nécessite beaucoup de code, mais ne vous inquiétez pas.
Il existe des bibliothèques hébergées sur OpenExchange pour effectuer facilement la plupart des opérations.
L'objectif de cet article est de fournir un exemple de manière à l'adapter à vos besoins, mais il ne s'agit pas d'un guide des meilleures pratiques en matière de sécurité.
Donc, créons notre sample.
### Outils et bibliothèques
- [config-api](https://openexchange.intersystems.com/package/Config-API): Cette bibliothèque sera utilisée pour configurer IRIS. Elle supporte la configuration du mirroring depuis la version 1.1.0. Nous ne donnerons pas une description détaillée de l'utilisation de cette bibliothèque. Un ensemble d'articles existe déjà. [ici](https://community.intersystems.com/post/environment-setup-config-api). En bref, config-api sera utilisé pour créer des fichiers de configuration (format JSON) et les charger facilement.
- [ZPM](https://openexchange.intersystems.com/package/ObjectScript-Package-Manager).
- Docker.
- OpenSSL.
### Page Github
Vous pouvez trouver tous les fichiers de ressources nécessaires sur [iris-mirroring-samples repository](https://github.com/lscalese/iris-mirroring-samples/).
### Préparation de votre système
Clonez le référentiel existant :
```bash
clone git https://github.com/lscalese/iris-mirroring-samples
cd iris-mirroring-samples
```
Si vous préférez créer un échantillon à partir de zéro, au lieu de cloner le référentiel, créez simplement un nouveau répertoire avec des sous-répertoires : `backup`, et `config-files`. Télécharger [irissession.sh](https://raw.githubusercontent.com/lscalese/iris-mirroring-samples/master/session.sh) :
```
mkdir -p iris-mirroring-samples/backup iris-mirroring-samples/config-files
cd iris-mirroring-samples
wget -O session.sh https://raw.githubusercontent.com/lscalese/iris-mirroring-samples/master/session.sh
```
Pour éviter le problème "permission denied" plus tard, nous devons créer le groupe `irisowner`, l'utilisateur `irisowner`, et changer le groupe du répertoire `backup` en `irisowner`
```bash
sudo useradd --uid 51773 --user-group irisowner
sudo groupmod --gid 51773 irisowner
sudo chgrp irisowner ./backup
```
Ce répertoire sera utilisé comme volume pour partager une sauvegarde de la base de données après avoir configuré le premier membre miroir avec les autres nœuds.
### Obtention d'une licence IRIS
La mise en miroir n'est pas disponible avec l'édition communautaire d'IRIS.
Si vous ne disposez pas encore d'une licence conteneur IRIS valide, connectez-vous au Centre de réponse mondial [Worldwide Response Center (WRC)](https://wrc.intersystems.com) avec vos informations d'identification.
Cliquez sur "Actions" --> "Online distribtion" ("Actions" --> "Distribution en ligne"), puis sur le bouton "Evaluations" et sélectionnez "Evaluation License" ("Licence d'évaluation") ; remplissez le formulaire.
Copiez votre fichier de licence `iris.key` dans ce répertoire.
### Connexion au Registre des conteneurs d'Intersystems
Pour des raisons pratiques, nous utilisons le Registre des conteneurs d'Intersystems (Intersystems Containers Registry) (ICR) pour extraire les images docker. Si vous ne connaissez pas votre login/mot de passe docker, connectez-vous simplement à [SSO.UI.User.ApplicationTokens.cls](https://login.intersystems.com/login/SSO.UI.User.ApplicationTokens.cls) avec vos informations d'identification WRC, et vous pourrez récupérer votre Token ICR.
```bash
docker login -u="YourWRCLogin" -p="YourICRToken" containers.intersystems.com
```
### Création de la base de données `myappdata` et d'un mapping global
Nous ne créons pas vraiment la base de données `myappdata` maintenant mais préparons une configuration pour la créer au moment de la construction l'image.
Pour cela, nous créons juste un simple fichier au format JSON ;
La bibliothèque config-api sera utilisée pour le charger dans les instances IRIS.
Creation du ficher [config-files/simple-config.json](https://github.com/lscalese/iris-mirroring-samples/blob/master/config-files/simple-config.json)
```json
{
"Defaults":{
"DBDATADIR" : "${MGRDIR}myappdata/",
"DBDATANAME" : "MYAPPDATA"
},
"SYS.Databases":{
"${DBDATADIR}" : {}
},
"Databases":{
"${DBDATANAME}" : {
"Directory" : "${DBDATADIR}"
}
},
"MapGlobals":{
"USER": [{
"Name" : "demo.*",
"Database" : "${DBDATANAME}"
}]
},
"Security.Services" : {
"%Service_Mirror" : { /* Activer le service miroir sur cette instance */
"Enabled" : true
}
}
}
```
Ce fichier de configuration vous permet de créer une nouvelle base de données avec les paramètres par défaut et de faire un global mapping `demo.*` dans l'espace de noms USER.
Pour plus d'informations sur les capacités du fichier de configuration [config-api](https://openexchange.intersystems.com/package/config-api), consultez l'[article](https://community.intersystems.com/post/environment-setup-config-api) ou la [page github](https://community.intersystems.com/post/environment-setup-config-api)
### Dockerfile
Le Dockerfile est basé sur le modèle existant [docker template](https://github.com/intersystems-community/objectscript-docker-template), mais nous devons faire quelques changements pour créer un répertoire de travail, installer les outils pour utiliser l'IP virtuelle, installer ZPM, etc…
Notre image IRIS est la même pour chaque membre du miroir. Le miroir sera mis en place sur le conteneur en commençant par la configuration correcte selon son rôle (primary, backup, ou report async r\w). Voir les commentaires sur le Dockerfile ci-dessous :
```Dockerfile
ARG IMAGE=containers.intersystems.com/intersystems/iris:2021.1.0.215.0
# Il n'est pas nécessaire de télécharger l'image depuis WRC. Elle sera tirée de l'ICR au moment de la construction.
FROM $IMAGE
USER root
COPY session.sh /
COPY iris.key /usr/irissys/mgr/iris.key
# /opt/demo sera notre répertoire de travail utilisé pour stocker nos fichiers de configuration et autres fichiers d'installation.
# Installez iputils-arping pour avoir une commande arping. Nécessaire pour configurer l'IP virtuelle.
# Téléchargez la dernière version de ZPM (ZPM est inclus uniquement dans l'édition communautaire).
RUN mkdir /opt/demo && \
chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/demo && \
chmod 666 /usr/irissys/mgr/iris.key && \
apt-get update && apt-get install iputils-arping gettext-base && \
wget -O /opt/demo/zpm.xml https://pm.community.intersystems.com/packages/zpm/latest/installer
USER ${ISC_PACKAGE_MGRUSER}
WORKDIR /opt/demo
# Définissez le rôle du miroir par défaut comme assistant.
# La valeur sera remplacée dans le fichier docker-compose au moment de l'exécution.
ARG IRIS_MIRROR_ROLE=master
# Copiez le contenu du répertoire config-files dans /opt/demo.
# Actuellement, nous n'avons créé qu'un simple-config pour configurer notre base de données et le mapping global.
# Plus tard dans cet article, nous ajouterons d'autres fichiers de configuration pour mettre en place le miroir.
ADD config-files .
SHELL [ "/session.sh" ]
# Installez ZPM
# Utilisez ZPM pour installer config-api
# Chargez le fichier simple-config.json avec config-api pour :
# - créer la base de données "myappdata",
# - ajouter un mapping global dans l'espace de nom "USER" pour la globale "demo.*" sur la base de données "myappdata".
# Fondamentalement, le point d'entrée pour installer votre application ObjectScript est ici.
# Pour cet exemple, nous allons charger simple-config.json pour créer une base de données simple et un mapping global.
RUN \
Do $SYSTEM.OBJ.Load("/opt/demo/zpm.xml", "ck") \
zpm "install config-api" \
Set sc = ##class(Api.Config.Services.Loader).Load("/opt/demo/simple-config.json")
# Copiez le script d'initialisation du miroir.
COPY init_mirror.sh /
```
### Construction de l'image IRIS
Le Dockerfile est prêt ; nous pouvons construire l'image :
```
docker build --no-cache --tag mirror-demo:latest .
```
Cette image sera utilisée pour exécuter les nœuds primary, backup et de report async.
### The .env file
Les fichiers de configuration JSON et docker-compose utilisent des variables d'environnement.
Leurs valeurs sont stockées dans un fichier nommé `.env`, pour ce sample notre fichier env est :
```
APP_NET_SUBNET=172.16.238.0/24
MIRROR_NET_SUBNET=172.16.220.0/24
IRIS_HOST=172.16.238.100
IRIS_PORT=1972
IRIS_VIRTUAL_IP=172.16.238.100
ARBITER_IP=172.16.238.10
MASTER_APP_NET_IP=172.16.238.20
MASTER_MIRROR_NET_IP=172.16.220.20
BACKUP_APP_NET_IP=172.16.238.30
BACKUP_MIRROR_NET_IP=172.16.220.30
REPORT_APP_NET_IP=172.16.238.40
REPORT_MIRROR_NET_IP=172.16.220.40
```
### Préparation du fichier de configuration du premier membre du miroir
La bibliothèque config-api permet de configurer un miroir, nous devons donc créer un fichier de configuration dédié au premier membre du miroir : `config-files/mirror-master.json`
Pour plus de commodité, les commentaires sont situés directement dans le JSON. Vous pouvez télécharger le fichier [mirror-master.json sans commentaire ici] (https://raw.githubusercontent.com/lscalese/iris-mirroring-samples/master/config-files/mirror-master.json).
```json
{
"Security.Services" : {
"%Service_Mirror" : {
"Enabled" : true
}
},
"SYS.MirrorMaster" : {
"Demo" : {
"Config" : {
"Name" : "Demo", /* Le nom de notre miroir */
"SystemName" : "master", /* Le nom de cette instance dans le miroir */
"UseSSL" : true,
"ArbiterNode" : "${ARBITER_IP}|2188", /* L'adresse IP et port du nœud arbitre */
"VirtualAddress" : "${IRIS_VIRTUAL_IP}/24", /* L'adresse IP virtuelle */
"VirtualAddressInterface" : "eth0", /* L'interface réseau utilisée pour l'adresse IP virtuelle. */
"MirrorAddress": "${MASTER_MIRROR_NET_IP}", /* L'adresse IP de ce noeud dans le réseau miroir privé */
"AgentAddress": "${MASTER_APP_NET_IP}" /* L'adresse IP de ce nœud (l'agent est installé sur la même machine) */
},
"Databases" : [{ /* La liste des bases de données à ajouter au miroir */
"Directory" : "/usr/irissys/mgr/myappdata/",
"MirrorDBName" : "MYAPPDATA"
}],
"SSLInfo" : { /* SSL Configuration */
"CAFile" : "/certificates/CA_Server.cer",
"CertificateFile" : "/certificates/master_server.cer",
"PrivateKeyFile" : "/certificates/master_server.key",
"PrivateKeyPassword" : "",
"PrivateKeyType" : "2"
}
}
}
}
```
### Préparer le fichier de configuration du membre de basculement
Créer un fichier de configuration pour le membre backup (basculement) `config-files/mirror-backup.json`.
Le fichier est fort ressemblant au fichier de configuration du membre primary:
```json
{
"Security.Services" : {
"%Service_Mirror" : {
"Enabled" : true
}
},
"SYS.MirrorFailOver" : {
"Demo" : { /* Le miroir à rejoindre */
"Config": {
"Name" : "Demo",
"SystemName" : "backup", /* Le nom de cette instance dans le miroir */
"InstanceName" : "IRIS", /* Le nom de l'instance IRIS du premier membre du miroir */
"AgentAddress" : "${MASTER_APP_NET_IP}", /* L'adresse IP de l'agent du premier membre du miroir */
"AgentPort" : "2188",
"AsyncMember" : false,
"AsyncMemberType" : ""
},
"Databases" : [{ /* DB dans le miroir */
"Directory" : "/usr/irissys/mgr/myappdata/"
}],
"LocalInfo" : {
"VirtualAddressInterface" : "eth0", /* L'interface réseau utilisée pour l'adresse IP virtuelle */
"MirrorAddress": "${BACKUP_MIRROR_NET_IP}" /* L'adresse IP de ce noeud dans le réseau miroir privé */
},
"SSLInfo" : {
"CAFile" : "/certificates/CA_Server.cer",
"CertificateFile" : "/certificates/backup_server.cer",
"PrivateKeyFile" : "/certificates/backup_server.key",
"PrivateKeyPassword" : "",
"PrivateKeyType" : "2"
}
}
}
}
```
### Préparation du fichier de configuration des membres asynchrones en lecture-écriture
Il est assez similaire au fichier de configuration de basculement backup. Les différences sont les valeurs de `AsyncMember`, `AsyncMemberType`, et `MirrorAddress`.
Créez le fichier `./config-files/mirror-report.json` :
```json
{
"Security.Services" : {
"%Service_Mirror" : {
"Enabled" : true
}
},
"SYS.MirrorFailOver" : {
"Demo" : {
"Config": {
"Name" : "Demo",
"SystemName" : "report",
"InstanceName" : "IRIS",
"AgentAddress" : "${MASTER_APP_NET_IP}",
"AgentPort" : "2188",
"AsyncMember" : true,
"AsyncMemberType" : "rw"
},
"Databases" : [{
"Directory" : "/usr/irissys/mgr/myappdata/"
}],
"LocalInfo" : {
"VirtualAddressInterface" : "eth0",
"MirrorAddress": "${REPORT_MIRROR_NET_IP}"
},
"SSLInfo" : {
"CAFile" : "/certificates/CA_Server.cer",
"CertificateFile" : "/certificates/report_server.cer",
"PrivateKeyFile" : "/certificates/report_server.key",
"PrivateKeyPassword" : "",
"PrivateKeyType" : "2"
}
}
}
}
```
### Génération des certificats et configuration des nœuds IRIS et
Tous les fichiers de configuration sont prêts !
Maintenant nous devons ajouter un script pour générer des certificats afin de sécuriser la communication entre chaque nœud. Un script prêt à l'emploi est disponible dans le repository [gen-certificates.sh](https://raw.githubusercontent.com/lscalese/iris-mirroring-samples/master/gen-certificates.sh)
```
# sudo est nécessaire en raison de l'utilisation de chown, chgrp chmod.
sudo ./gen-certificates.sh
```
Pour configurer chaque nœud, `init_mirror.sh` sera exécuté au démarrage des conteneurs. Il sera configuré plus tard dans `docker-compose.yml` dans la section commande `command : ["-a", "/init_mirror.sh"]` :
```bash
#!/bin/bash
# Base de données utilisée pour tester le miroir.
DATABASE=/usr/irissys/mgr/myappdata
# Répertoire contenant mes données d'application sauvegardées par l'assistant pour les restaurer sur les autres nœuds et en faire un miroir.
BACKUP_FOLDER=/opt/backup
# Fichier de configuration miroir au format json config-api pour le nœud d'assistant.
MASTER_CONFIG=/opt/demo/mirror-master.json
# Fichier de configuration miroir au format json config-api pour le nœud de sauvegarde.
BACKUP_CONFIG=/opt/demo/mirror-backup.json
# Fichier de configuration miroir au format json config-api pour le nœud asynchrone de rapport.
REPORT_CONFIG=/opt/demo/mirror-report.json
# Nom du miroir...
MIRROR_NAME=DEMO
# Liste des membres du miroir.
MIRROR_MEMBERS=BACKUP,REPORT
# Chargement de la configuration du miroir en utilisant config-api avec le fichier /opt/demo/simple-config.json.
# Démarrage d'une tâche Job pour accepter automatiquement d'autres membres nommés "backup" et "report" pour rejoindre le miroir (éviter la validation manuelle dans la gestion du portail).
master() {
rm -rf $BACKUP_FOLDER/IRIS.DAT
envsubst < ${MASTER_CONFIG} > ${MASTER_CONFIG}.resolved
iris session $ISC_PACKAGE_INSTANCENAME -U %SYS
Article
Guillaume Rongier · Juil 22, 2022
IntroductionDepuis la version 2019.2, InterSystems IRIS fournit son API native pour Python comme méthode d'accès aux données haute performance. L'API native vous permet d'interagir directement avec la structure de données IRIS native.GlobalesEn tant que développeurs InterSystems, vous êtes probablement déjà familiarisés avec les globales. Nous allons passer en revue les bases au cas où vous souhaiteriez un rafraîchissement, mais n'hésitez pas à passer directement à la section suivante.InterSystems IRIS utilise des globales pour stocker les données. Une globale est un tableau clairsemé qui se compose de nœuds qui peuvent ou non avoir une valeur et de sous-nœuds. Ce qui suit est un exemple abstrait d'une globale :Dans cet exemple, a il y a un nœud racine, appelé nom global. Chaque nœud possède une adresse de nœud qui se compose du nom global et d'un ou plusieurs indices (noms des sous-nœuds). a possède les indices b and c; l'adresse de ces noeuds est a->b et a->c.Les noeuds a->b et a->c->g ont une valeur (d et h), les noeuds a->b->e et a->b->f n'ont aucune valeur. Le noeud a->b possède les indices e et f.Une description approfondie de cette structure peut être trouvée dans le livre InterSystems "Utilisation des globaux" ("Using Globals").Lecture et écriture dans la globaleNative Python API permet de lire et d'écrire directement des données dans la globale IRIS. Le paquet irisnative est disponible sur GitHub — ou si InterSystems IRIS est installé localement sur votre machine, vous le trouverez dans le sous-répertoire dev/python de votre répertoire d'installation.La fonction irisnative.createConnection vous permet de créer une connexion à IRIS et la fonction irisnative.createIris vous donne un objet de cette connexion avec lequel nous pouvons manipuler la globale. Cet objet possède les méthodes get et set pour lire/écrire depuis/vers la globale, et une méthode kill pour supprimer un nœud et ses sous-nœuds. Il possède également une méthode isDefined qui renvoie 0 si le noeud demandé n'existe pas ; 1 s'il a une valeur, mais pas de descendants ; 10 s'il n'a pas de valeur et a des descendants ; ou 11 s'il a une valeur et des descendants.
import irisnative
conn = irisnative.createConnection("127.0.0.1", 51773, "USER", "", "")
iris = irisnative.createIris(conn)
iris.set("value", "root", "sub1", "sub2") # sets "value" to root->sub1->sub2
print(iris.get("root", "sub1", "sub2"))
print(iris.isDefined("root", "sub1"))
iris.kill("root")
conn.close()
Il dispose également d'une méthode iterator pour boucler sur les sous-nœuds d'un certain nœud. (L'utilisation sera démontrée dans la section suivante.)
Pour une description complète de chaque méthode, reportez-vous à la documentation de l'interface API .
Les fichiers de données sur le trafic de San Francisco GTFS
Stockage des données dans la globale
La Spécification générale des flux de transport en commun (GTFS) est un format pour les horaires et les itinéraires des transports publics.
Regardons comment nous pouvons utiliser l'API native IRIS pour travailler avec les données GTFS de San Francisco à partir du 10 juin 2019.
Tout d'abord, nous allons stocker les informations des fichiers de données dans la globale IRIS. (Tous les fichiers et toutes les colonnes ne seront pas utilisés dans cette démo). Les fichiers sont au format CSV, où la première ligne indique les noms des colonnes et toutes les autres lignes contiennent les données. En Python, nous commencerons par effectuer les importations nécessaires et établir une connexion à IRIS :
import csv
import irisnative
conn = irisnative.createConnection("127.0.0.1", 51773, "USER", "", "")
iris = irisnative.createIris(conn)
Sur la base des noms de colonnes et des données, nous pouvons construire une arborescence judicieuse pour chaque fichier et utiliser iris.set pour stocker les données dans la globale.
Commençons par le fichier stops.txt, qui contient tous les arrêts de transport public de la ville. Dans ce fichier, nous n'utiliserons que les colonnes stop_id et stop_name. Nous allons les stocker dans une globale nommée stops une structure arborescente avec une couche de nœuds, avec les ID des arrêts comme indices et le nom des arrêts comme valeurs des nœuds. Notre structure ressemble donc à stops → [stop_id]=[stop_name]. (Pour cet article, j'utiliserai des crochets pour indiquer quand un indice n'est pas littéral, mais plutôt une valeur lue dans les fichiers de données.)
with open("stops.txt", "r") as csvfile:
reader = csv.reader(csvfile)
next(reader) # Ignorez les noms des colonnes
# stops -> [stop_id]=[stop_name]
for row in reader:
iris.set(row[6], "stops", row[4])
csv.reader retourne un itérateur de listes qui contiennent les valeurs séparées par des virgules. La première ligne contient les noms des colonnes, nous allons donc la sauter avec next(reader). Nous utiliserons iris.set pour définir le nom de l'arrêt comme valeur de stops -> [stop_id].
Ensuite, il y a le fichier routes.txt dont nous utiliserons les colonnes route_type, route_id, route_short_name et route_long_name. Une structure globale raisonnable est routes -> [route_type] -> [route_id] -> [route_short_name]=[route_long_name]. (Le type d'itinéraire est 0 pour un tram, 3 pour un bus et 5 pour un téléphérique.) Nous pouvons lire le fichier CSV et placer les données dans la globale exactement de la même manière.
with open("routes.txt", "r") as csvfile:
reader = csv.reader(csvfile)
next(reader) # Ignorez les noms des colonnes
# routes -> [route_type] -> [route_id] -> [route_short_name]=[route_long_name]
for row in reader:
iris.set(row[0], "routes", row[1], row[5], row[8])
Chaque itinéraire a des trajets trips, stockés dans trips.txt, dont nous utiliserons les colonnes route_id, direction_id, trip_headsign et trip_id. Les trajets sont identifiés de manière unique par leur trip ID (que nous verrons plus tard dans le fichier des temps d'arrêt). Les trajets sur une route peuvent être séparés en deux groupes en fonction de leur direction, et les directions sont associées à des panneaux de tête. Cela conduit à la structure arborescente trips -> [route_id] -> [direction_id]=[trip_headsign] -> [trip_id].
Nous avons besoin de deux appels iris.set ici — un pour définir la valeur du nœud ID de la direction, et un pour créer le nœud sans valeur de l'ID du trajet.
with open("trips.txt", "r") as csvfile:
reader = csv.reader(csvfile)
next(reader) # Ignorez les noms des colonnes
# trips -> [route_id] -> [direction_id]=[trip_headsign] ->[trip_id]
for row in reader:
iris.set(row[3], "trips", row[1], row[2])
iris.set(None, "trips", row[1], row[2], row[6])
Enfin, nous allons lire et stocker les temps d'arrêt. Ils sont stockés dans le fichier stop_times.txt et nous allons utiliser les colonnes stop_id, trip_id, stop_sequence et departure_time. Une première option pourrait consister à utiliser stoptimes -> [stop_id] -> [trip_id] -> [departure_time] ou si nous voulons conserver la séquence des arrêts, stoptimes -> [stop_id] -> [trip_id] -> [stop_sequence]=[departure_time].
with open("stop_times.txt", "r") as csvfile:
reader = csv.reader(csvfile)
next(reader) # Ignorez les noms des colonnes
# stoptimes -> [stop_id] -> [trip_id] -> [stop_sequence]=[departure_time]
for row in reader:
iris.set(row[2], "stoptimes", row[3], row[0], row[4])
Interrogation des données à l'aide de l'API native
Ensuite, notre objectif est de trouver toutes les heures de départ pour l'arrêt avec le nom donné.
Tout d'abord, nous récupérons l'ID de l'arrêt à partir du nom de l'arrêt donné, puis nous utilisons cet ID pour trouver les heures pertinentes dans le fichier stop_times.
L'appel iris.iterator("stops") nous permet d'itérer sur les sous-nœuds du nœud racine stops. Nous voulons itérer sur les paires d'indices et de valeurs (pour comparer les valeurs avec le nom donné, et connaître immédiatement l'indice s'il correspond), nous appelons donc .items() sur l'itérateur, ce qui définit le type de retour en tuples (indice, valeur). Nous pouvons alors itérer sur tous ces tuples et trouver le bon arrêt.
stop_name = "Silver Ave & Holyoke St"
iter = iris.iterator("stops").items()
stop_id = None
for item in iter:
if item[1] == stop_name:
stop_id = item[0]
break
if stop_id is None:
print("Stop not found.")
import sys
sys.exit()
Il convient de noter que la recherche d'une clé par sa valeur par itération n'est pas très efficace s'il y a beaucoup de nœuds. Une façon d'éviter cela serait d'avoir un autre tableau, où les indices sont les noms des arrêts et les valeurs sont les IDs. La recherche de la valeur --> clé consisterait alors en une requête dans ce nouveau tableau.
Vous pouvez également utiliser le nom de l'arrêt comme identifiant partout dans votre code au lieu de l'ID de l'arrêt - le nom de l'arrêt est également unique.
Comme vous pouvez le voir, si nous avons une quantité importante d'arrêts, cette recherche peut prendre un certain temps - elle est également connue sous le nom de "balayage complet". Mais nous pouvons profiter des globales et construire le tableau inversé où les noms seront les clés et les IDs les valeurs.
iter = iris.iterator("stops").items()
stop_id = None
for item in iter:
iris.set(item[0], "stopnames", item[1])
En disposant de la globale de stopnames, où l'index est le nom et la valeur est l'ID, le code ci-dessus pour trouver le stop_id par le nom sera remplacé par le code suivant qui s'exécutera sans une recherche par balayage complet :
stop_name = "Silver Ave & Holyoke St"
stop_id=iris.get("stopnames", stop_name)
if stop_id is None:
print("Stop not found.")
import sys
sys.exit()
À ce stade, nous pouvons trouver les heures d'arrêt. Le sous-arbre stoptimes -> [stop_id] contient les ID des trajets en tant que sous-nœuds, qui contiennent les temps d'arrêt en tant que sous-nœuds. Nous ne sommes pas intéressés par les ID de trajet - seulement par les temps d'arrêt - donc nous allons itérer sur tous les ID de trajet et collecter tous les temps d'arrêt pour chacun d'eux.
all_stop_times = set()
trips = iris.iterator("stoptimes", stop_id).subscripts()
for trip in trips:
all_stop_times.update(iris.iterator("stoptimes", stop_id, trip).values())
Nous n'utilisons pas .items() sur l'itérateur ici, mais nous utiliserons .subscripts() et .values() car les ID de trajet sont des subscripts (sans valeurs associées) ou la couche inférieure ([stop_sequence]=[departure_time]), nous sommes seulement intéressés par les valeurs et les heures de départ. L'appel .update ajoute tous les éléments de l'itérateur à notre ensemble existant. L'ensemble contient maintenant toutes les heures d'arrêt (uniques) :
for stop_time in sorted(all_stop_times):
print(stop_time)
Rendons les choses un peu plus compliquées. Au lieu de trouver toutes toutes les heures de départ d'un arrêt, nous allons trouver uniquement les heures de départ d'un arrêt pour un itinéraire donné (dans les deux sens) où l'ID de l'itinéraire est donné. Le code permettant de trouver l'ID de l'arrêt à partir du nom de l'arrêt peut être conservé dans son intégralité. Ensuite, tous les ID des arrêts sur l'itinéraire donné seront récupérés.
Le sous-arbre de trips -> [route_id] est divisé en deux directions, qui ont tous les ID de trajets comme sous-nœuds. Nous pouvons itérer sur les directions comme précédemment, et ajouter tous les sous-noeuds des directions à un ensemble.
route = "14334"
selected_trips = set()
directions = iris.iterator("trips", route).subscripts()
pour la direction dans les directions :
selected_trips.update(iris.iterator("trips", route, direction).subscripts())
L'étape suivante consiste à trouver les valeurs de tous les sous-nœuds de stoptimes -> [stop_id] -> [trip_id] où [stop_id] est l'identifiant de l'arrêt récupéré et [trip_id] est l'un des identifiants de trajet sélectionnés. Nous itérons sur l'ensemble selected_trips pour trouver toutes les valeurs pertinentes :
all_stop_times = set()
pour le trajet dans selected_trips:
all_stop_times.update(iris.iterator("stoptimes", stop_id, trip).values())
pour stop_time dans sorted(all_stop_times):
print(stop_time)
Un dernier exemple montre l'utilisation de la fonction isDefined. Nous allons développer le code écrit précédemment : au lieu de coder en dur l'ID de la route, le nom court d'une route est donné, puis l'ID de la route doit être récupéré sur cette base. Les noeuds avec les noms de route sont sur la couche inférieure de l'arbre. La couche supérieure contient les ID des routes. Nous pouvons itérer sur tous les types de route, puis sur tous les ID de route, et si le noeud routes -> [route_type] -> [route_id] -> [route_short_name] existe et a une valeur (isDefined retourne 1), alors nous savons que [route_id] est l'ID que nous recherchons.
route_short_name = "44"
route = None
types = iris.iterator("routes").subscripts()
for type in types:
route_ids = iris.iterator("routes", type).subscripts()
for route_id in route_ids:
if iris.isDefined("routes", type, route_id, route_short_name) == 1:
route = route_id
if route is None:
print("No route found.")
import sys
sys.exit()
Ce code sert à remplacer la ligne route = "14334" codée en dur.
Lorsque toutes les opérations IRIS sont terminées, nous pouvons fermer la connexion à la base de données :
conn.close()
Prochaines étapes
Nous avons couvert comment l'API native de Python peut être utilisée pour accéder à la structure de données d'InterSystems IRIS, puis être appliquée aux données des transports publics de San Francisco. Pour une plongée plus profonde dans l'API, vous pouvez consulter la documentation . L'API native est également disponible pour Java, .NET et Node.js.
Article
Guillaume Rongier · Avr 7, 2023
Suite à la [partie précédente](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i), il est temps de tirer parti de l'instruction de VALIDATION DU MODÈLE IntegratedML, qui fournit des informations permettant de surveiller vos modèles ML. Vous pouvez la voir en action [ici](https://www.youtube.com/watch?v=q9ORM32zPjs)
Le code présenté ici est dérivé d'exemples fournis par le [modèle InterSystems IntegratedML](https://openexchange.intersystems.com/package/integratedml-demo-template) ou la [documentation IRIS](https://irisdocs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCM_healthmon), documentation
Remarque: Le code présenté ici n'a qu'une valeur explicative. Si vous souhaitez l'essayer, j'ai développé une application modèle - [iris-integratedml-monitor-example](https://openexchange.intersystems.com/package/iris-integratedml-monitor-example), qui participe au concours IA d'InterSystems IRIS (InterSystems IRIS AI Contest). S'il vous plaît, après avoir lu cet article, vous pouvez le consulter et, si vous l'aimez, [votez pour moi](https://openexchange.intersystems.com/contest/current)! :)
# Contenu
### Partie I:
* [Les systèmes IRIS IntegratedML et ML](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i#iris_integratedml_and_ml_systems)
* [Entre les anciennes et les nouvelles normalités](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i#between_the_old_and_new_normal)
### Partie II:
* [Surveillance des performances du ML](#monitoring_ml_performance)
* [Un cas d'utilisation simple](#a_simple_use_case)
* [Travaux futurs](#future_works)
# Surveillance des performances du ML
Pour surveiller votre modèle ML, vous aurez besoin d'au moins deux fonctions :
1) Fournisseur de mesures de performance
2) Service de surveillance et de notification
Heureusement, IRIS nous fournit ces deux caractéristiques nécessaires.
## Obtenir des mesures de performance des modèles ML
Comme nous l'avons vu dans la [partie précédente](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i), IntegratedML fournit l'instruction VALIDER LE MODÈLE pour calculer les paramètres de performance suivants :
* Exactitude : la qualité de votre modèle (des valeurs proches de 1 signifient des taux élevés de réponses correctes).
* Précision : dans quelle mesure votre modèle traite les faux positifs (des valeurs proches de 1 signifient un taux élevé de **non** faux positifs).
* Rappel : dans quelle mesure votre modèle traite les faux négatifs (des valeurs proches de 1 signifient un taux élevé de **non** faux négatifs).
* Mesure F : une autre façon de mesurer la précision, utilisée lorsque la précision n'est pas satisfaisante (des valeurs proches de 1 signifient un taux élevé de réponses correctes).
Remarque: ces définitions ne sont pas formelles, elles sont même assez superficielles ! Je vous encourage à prendre le temps de les [interpréter](https://medium.com/analytics-vidhya/accuracy-vs-f1-score-6258237beca2).
Ce qui est intéressant, c'est qu'à chaque fois que vous appelez VALIDER LE MODÈLE (VALIDATE MODEL), IntegrateML enregistre sa mesure de performance, et nous pouvons tirer parti de cette fonctionnalité pour la surveiller.
## Moteur de surveillance
InterSystems IRIS fournit le framework du Moniteur de Système pour traiter les tâches de surveillance. Il vous permet également de définir des règles personnalisées afin de déclencher des notifications basées sur des prédicats appliqués à ces mesures.
Par défaut, un ensemble de mesures est fourni pour le disque, la mémoire, les processus, le réseau, etc. En outre, le Moniteur de Système vous permet également d'étendre les moniteurs pour couvrir une infinité de possibilités. De tels moniteurs personnalisés sont appelés une application de moniteur (Application Monitor) dans la terminologie de moniteur de système (System Monitor).
Vous pouvez obtenir plus d'informations sur le Moniteur de Système [ici](https://irisdocs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCM_healthmon).
## La mise en place de l'ensemble
Jusqu'à présent, nous avons un moyen d'obtenir les valeurs des métriques de performance de chaque validation du modèle et, un outil qui pourrait déclencher des alertes basées sur des règles personnalisées appliquées à des sources de métriques personnalisées... Bien, il est temps de les combiner.
Tout d'abord, nous devons créer une classe de moniteur d'application personnalisée, en étendant la classe %Monitor.Abstract et en implémentant les méthodes *Initialize* et *GetSample*.
```
Class MyMetric.IntegratedMLModelsValidation Extends %Monitor.Adaptor
{
/// Initialiser la liste des métriques de validation des modèles.
Method Initialize() As %Status
{
Return $$$OK
}
/// Obtenir un échantillon métrique de routine.
/// Un code de retour de $$$OK indique qu'il y a une nouvelle instance d'échantillon.
/// Tout autre code de retour indique qu'il n'y a pas d'échantillon.
Method GetSample() As %Status
{
Return $$$OK
}
}
```
Les moniteurs système émettent des appels réguliers aux classes de moniteurs afin d'obtenir un ensemble de mesures appelées échantillons. Ces échantillons peuvent être simplement surveillés ou utilisés pour vérifier si des règles d'alerte doivent être levées. Vous définissez la structure de ces échantillons en définissant des propriétés standard non internes dans la classe monitior. Il est important de souligner ici que vous devez spécifier, dans le paramètre INDEX, l'une de ces propriétés comme clé primaire de chaque échantillon - sinon une erreur de clé dupliquée sera générée.
```
Class MyMetric.IntegratedMLModelsValidation1 Extends %Monitor.Adaptor
{
Parameter INDEX = "ModelTrainedName";
/// Nom de la définition du modèle
Property ModelName As %Monitor.String;
/// Nom du modèle formé en cours de validation
Property ModelTrainedName As %Monitor.String;
/// Erreur de validation (le cas échéant)
Property StatusCode As %Monitor.String;
/// Précision
Property ModelMetricPrecision As %Monitor.Numeric;
/// Rappel
Property ModelMetricRecall As %Monitor.Numeric;
/// Mesure F
Property ModelMetricFMeasure As %Monitor.Numeric;
/// Exactitude
Property ModelMetricAccuracy As %Monitor.Numeric;
...
}
```
La méthode *Initialize* est appelée une fois pour chaque appel de moniteur et la méthode *GetSample* est appelée jusqu'à ce qu'elle renvoie 0.
Ainsi, nous pourrions mettre en place un SQL sur l'historique de validation d'IntegrateML pour fournir des informations de métriques au moniteur, en implémentant les méthodes *Initialize* et *GetSample* :
```
/// Initialiser la liste des métriques de validation des modèles.
Method Initialize() As %Status
{
// Obtenir la dernière validation pour chaque modèle validé par l'instruction VALIDATION DU MODÈLE
Set sql =
"SELECT MODEL_NAME, TRAINED_MODEL_NAME, STATUS_CODE, %DLIST(pair) AS METRICS_LIST FROM ("_
"SELECT m.*, $LISTBUILD(m.METRIC_NAME, m.METRIC_VALUE) pair, r.STATUS_CODE "_
"FROM INFORMATION_SCHEMA.ML_VALIDATION_RUNS r "_
"JOIN INFORMATION_SCHEMA.ML_VALIDATION_METRICS m "_
"ON m.MODEL_NAME = r.MODEL_NAME "_
"AND m.TRAINED_MODEL_NAME = r.TRAINED_MODEL_NAME "_
"AND m.VALIDATION_RUN_NAME = r.VALIDATION_RUN_NAME "_
"GROUP BY m.MODEL_NAME, m.METRIC_NAME "_
"HAVING r.COMPLETED_TIMESTAMP = MAX(r.COMPLETED_TIMESTAMP)"_
") "_
"GROUP BY MODEL_NAME"
Set stmt = ##class(%SQL.Statement).%New()
$$$THROWONERROR(status, stmt.%Prepare(sql))
Set ..Rspec = stmt.%Execute()
Return $$$OK
}
/// Obtenir un échantillon métrique de routine.
/// Un code de retour de $$$OK indique qu'il y a une nouvelle instance d'échantillon.
/// Tout autre code de retour indique qu'il n'y a pas d'échantillon.
Method GetSample() As %Status
{
Set stat = ..Rspec.%Next(.sc)
$$$THROWONERROR(sc, sc)
// Quitter si nous avons fait tous les jeux de données
If 'stat {
Quit 0
}
// remplir cette instance
Set ..ModelName = ..Rspec.%Get("MODEL_NAME")
Set ..ModelTrainedName = ..Rspec.%Get("TRAINED_MODEL_NAME")_" ["_$zdt($zts,3)_"]"
Set ..StatusCode = ..Rspec.%Get("STATUS_CODE")
Set metricsList = ..Rspec.%Get("METRICS_LIST")
Set len = $LL(metricsList)
For iMetric = 1:1:len {
Set metric = $LG(metricsList, iMetric)
Set metricName = $LG(metric, 1)
Set metricValue = $LG(metric, 2)
Set:(metricName = "PRECISION") ..ModelMetricPrecision = metricValue
Set:(metricName = "RECALL") ..ModelMetricRecall = metricValue
Set:(metricName = "F-MEASURE") ..ModelMetricFMeasure = metricValue
Set:(metricName = "ACCURACY") ..ModelMetricAccuracy = metricValue
}
// quitter avec une valeur de retour indiquant que les données de l'échantillon sont prêtes
Return $$$OK
}
```
Après avoir compilé la classe de moniteur, vous devez redémarrer le moniteur de système System Monitor afin que le système comprenne qu'un nouveau moniteur a été créé et qu'il est prêt à être utilisé. Pour ce faire, vous pouvez utiliser la routine ^%SYSMONMGR ou la classe %SYS.Monitor.
# Un cas d'utilisation simple
Jusqu'à présent, nous disposons des outils nécessaires pour collecter, surveiller et émettre des alertes sur les mesures de performance du ML. Il est maintenant temps de définir une règle d'alerte personnalisée et de simuler un scénario dans lequel un modèle ML déployé commence à affecter négativement vos performances.
Tout d'abord, nous devons configurer une alerte par courriel et sa règle de déclenchement. Cela peut être fait à l'aide de la routine ^%SYSMONMGR. Cependant, afin de rendre les choses plus faciles, j'ai créé une méthode d'installation qui définit toute la configuration de l'e-mail et de la règle d'alerte. Vous devez remplacer les valeurs entre <> ; par les paramètres de votre serveur de messagerie et de votre compte.
```
ClassMethod NotificationSetup()
{
// Régler les paramètres de l'e-mail
Set sender = ""
Set password = ""
Set server = ""
Set port = ""
Set sslConfig = "default"
Set useTLS = 1
Set recipients = $LB("")
Do ##class(%Monitor.Manager).AppEmailSender(sender)
Do ##class(%Monitor.Manager).AppSmtpServer(server, port, sslConfig, useTLS)
Do ##class(%Monitor.Manager).AppSmtpUserName(sender)
Do ##class(%Monitor.Manager).AppSmtpPassword(password)
Do ##class(%Monitor.Manager).AppRecipients(recipients)
// La messagerie électronique comme méthode de notification par défaut
Do ##class(%Monitor.Manager).AppNotify(1)
// Activer les notifications par messagerie électronique
Do ##class(%Monitor.Manager).AppEnableEmail(1)
Set name = "perf-model-appointments-prediction"
Set appname = $namespace
Set action = 1
Set nmethod = ""
Set nclass = ""
Set mclass = "MyMetric.IntegratedMLModelsValidation"
Set prop = "ModelMetricAccuracy"
Set expr = "%1 < .8"
Set once = 0
Set evalmethod = ""
// Créer une alerte
Set st = ##class(%Monitor.Alert).Create(name, appname, action, nmethod, nclass, mclass, prop, expr, once, evalmethod)
$$$THROWONERROR(st, st)
// Relancer le moniteur
Do ##class(MyMetric.Install).RestartMonitor()
}
```
Dans la méthode précédente, une alerte sera émise si le moniteur obtient des valeurs de précision inférieures à 90 %.
Maintenant que notre règle d'alerte est configurée, créons, formons et validons un modèle de prédiction show/no-show avec les 500 premiers enregistrements et validons-le avec les 600 premiers enregistrements.
Remarque : Le paramètre *seed* sert uniquement à garantir la reproductibilité (c.-à-d. pas de valeurs aléatoires) et doit normalement être évité en production.
```
-- Création du modèle
CREATE MODEL AppointmentsPredection PREDICTING (Show) FROM MedicalAppointments USING {\"seed\": 3}
-- Formation à l'aide des 500 premiers enregistrements du jeu de données
TRAIN MODEL AppointmentsPredection FROM MedicalAppointments WHERE ID
Article
Guillaume Rongier · Août 24, 2022
Il y a quelque temps, j'ai présenté un nouveau pilote de Django pour IRIS. Maintenant, voyons comment utiliser Django avec IRIS en pratique.

_Remarque importante : l'utilisation de Django avec IRIS Community Edition ne fonctionnera pratiquement pas, Community Edition n'a que 5 connexions disponibles, et Django les utilisera très rapidement. Malheureusement, pour cette raison, je ne peux pas recommander cette méthode pour le développement de nouvelles applications, en raison de la difficulté à prévoir l'utilisation des licences._
## Lancement du projet Django
Tout d'abord, démarrons notre nouveau projet Django. Pour ce faire, nous devons d'abord installer Django lui-même.
pip install django
Ensuite, créez un projet nommé demo, cela permettra de créer un dossier de projets avec le même nom
django-admin startproject demo
cd demo
ou vous pouvez le faire dans un dossier existant
django-admin startproject main .
Cette commande va remplir quelques fichiers python pour nous.
.png)
Where,
* **manage.py:** utilitaire de ligne de commande qui vous permet d'interagir avec ce projet Django de diverses manières
* **main** le répertoire correspondant au paquetage Python de votre projet
* **main/\_\_init\_\_.py:** un fichier vide qui indique à Python que ce répertoire doit être considéré comme un package Python
* **main/settings.py**: paramètres/configuration pour ce projet Django
* **main/urls.py**: Les déclarations d'URL pour ce projet Django ; une "table des matières" de votre site alimenté par Django.
* **main/asci.py**: Un point d'entrée pour les serveurs web compatibles avec ASGI pour servir votre projet.
* **main/wsci.py**: Un point d'entrée pour les serveurs web compatibles avec WSGI pour servir votre projet.
Même à partir de ce point, nous pouvons commencer notre projet et il fonctionnera dans une certaine mesure
$ python manage.py runserver
Contrôle des modifications de fichiers avec StatReloader
Réalisation de vérifications du système...
La vérification du système n'a identifié aucun problème (0 désactivé). Vous avez 18 migration(s) non appliquée(s). Votre projet ne fonctionnera pas correctement tant que vous n'aurez pas appliqué les migrations pour les applications suivantes : admin, auth, contenttypes, sessions.
Lancez 'python manage.py migrate' pour les appliquer.
22 juillet 2022 - 15:24:12
Django version 4.0.6, en utilisant les paramètres 'main.settings'.
Démarrage du serveur de développement à l'adresse http://127.0.0.1:8000/
Quittez le serveur avec CONTROL-C.
Now you can go to the browser and open URL http://127.0.0.1:8000 there
.png)
## Ajout IRIS
Ajoutons l'accès à IRIS, et pour le faire nous devons installer quelques dépendances à notre projet, et la bonne façon de le faire, est de le définir dans un fichier nommé _requirements.txt avec ce contenu, où nous devons ajouter django comme une dépendance__
# Django itself
django>=4.0.0
Et ensuite, le pilote pour IRIS pour Django, est publié. _Malheureusement, InterSystems ne veut pas publier ses propres pilotes sur PyPI, nous devons donc les définir de cette horrible façon. Sachez qu'ils peuvent le supprimer à tout moment, et qu'il peut donc ne plus fonctionner à l'avenir. (S'il était dans PyPI, il serait installé comme une dépendance de django-iris, et ne serait pas nécessaire de le définir explicitement)_
# Pilote IRIS d'InterSystems pour Django et pilote DB-API d'InterSystems
django-iris==0.1.13
https://raw.githubusercontent.com/intersystems-community/iris-driver-distribution/main/DB-API/intersystems_irispython-3.2.0-py3-none-any.whl
Installez les dépendances définies dans ce fichier avec la commande
pip install -r requirements.txt
Maintenant, nous pouvons configurer notre projet pour utiliser IRIS, pour ce faire, nous devons mettre à jour le paramètre **DATABASES** dans le fichier _settings.py_, avec des lignes comme celle-ci, où NAME pointe vers l'espace de nom dans IRIS, et le port vers le SuperPort où IRIS est disponible
DATABASES = {
'default': {
'ENGINE': 'django_iris',
'NAME': 'USER',
'USER': '_SYSTEM',
'PASSWORD': 'SYS',
'HOST': 'localhost',
'PORT': 1982,
}
}
Django a un ORM, et des modèles stockés dans le projet, et cela nécessite de synchroniser les modèles Django avec la base de données en tant que tableaux. Par défaut, il y a quelques modèles liés à l'authentification. Et nous pouvons lancer le processus de migration maintenant
$ python manage.py migrate
Opérations à effectuer :
Appliquer toutes les migrations : admin, auth, contenttypes, sessions
Exécution des migrations :
Application de contenttypes.0001_initial... OK
Application d'auth.0001_initial... OK
Application d'admin.0001_initial... OK
Application d'admin.0002_logentry_remove_auto_add... OK
Application d'admin.0003_logentry_add_action_flag_choices... OK
Application de contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Application d'auth.0003_alter_user_email_max_length... OK
Application d'auth.0004_alter_user_username_opts... OK
Application d'auth.0005_alter_user_last_login_null... OK
Application d'auth.0006_require_contenttypes_0002... OK
Application d'auth.0007_alter_validators_add_error_messages... OK
Application d'auth.0008_alter_user_username_max_length... OK
Application d'auth.0009_alter_user_last_name_max_length... OK
Application d'auth.0010_alter_group_name_max_length... OK
Application d'auth.0011_update_proxy_permissions... OK
Application d'auth.0012_alter_user_first_name_max_length... OK
Application de sessions.0001_initial... OK
Si vous allez à l'IRIS, vous y trouverez des tableaux supplémentaires
.png)
## Définissez d'autres modèles
Il est temps d'ajouter certains de nos modèles. Pour ce faire, ajoutez un nouveau fichier _models.py,_ avec un contenu comme suit
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
dob = models.DateField()
sex = models.BooleanField()
Comme vous pouvez le voir, il comporte différents types de champs. Ensuite, ce modèle doit être préparé pour la base de données. Avant de le faire, ajoutez notre projet **main** à **INSTALLED_APPS** dans _settings.py_
INSTALLED_APPS = [
....
'main',
]
Et nous pouvons exécuter makemigrations. Cette commande doit être appelée après toute modification du modèle, elle s'occupe des modifications historiques du modèle, et quelle que soit la version de l'application installée, la migration saura mettre à jour le schéma de la base de données
$ python manage.py makemigrations main
Migrations pour 'main':
main/migrations/0001_initial.py
- Créez le modèle Person
Nous pouvons exécuter migrate à nouveau, il sait déjà que les migrations précédentes ont déjà été effectuées, donc, il exécute seulement la nouvelle migration
$ python manage.py migrate
Opérations à effectuer :
Application de toutes les migrations : admin, auth, contenttypes, main, sessions
Exécution des migrations :
Application de main.0001_initial... OK
Et actuellement, nous pouvons voir comment la migration se présente du point de vue de SQL
$ python manage.py sqlmigrate main 0001
--
-- Créez le modèle Person
--
CREAEZ TABLEAU "main_person" ("id" BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY, "first_name" VARCHAR(30) NULL, "last_name" VARCHAR(30) NULL, "dob" DATE NOT NULL, "sex" BIT NOT NULL);
Mais il est possible d'avoir accès aux tableaux déjà existants dans la base de données, par exemple, si vous avez déjà une application qui fonctionne. J'ai installé le paquet zpm posts-and-tags, faisons un modèle pour le tableau community.posts
$ python manage.py inspectdb community.post
# Ceci est un module de modèle Django généré automatiquement.
# Vous devrez faire ce qui suit manuellement pour nettoyer tout ceci :
# * Réorganiser l'ordre des modèles
# * S'assurer que chaque modèle a un champ avec primary_key=True
# * Assurez-vous que chaque ForeignKey, et OneToOneField a un `on_delete` réglé sur le comportement désiré.
# * Supprimez les lignes `managed = False` si vous souhaitez autoriser Django à créer, modifier et supprimer le tableau.
# N'hésitez pas à renommer les modèles, mais ne renommez pas les valeurs de db_table ou les noms de champs
à partir de django.db import models
classe CommunityPost(models.Model):
id = models.AutoField(db_column='ID') # Nom du champ en minuscules.
acceptedanswerts = models.DateTimeField(db_column='AcceptedAnswerTS', blank=True, null=True) # Nom du champ en minuscules.
author = models.CharField(db_column='Author', max_length=50, blank=True, null=True) # Nom du champ en minuscules.
avgvote = models.IntegerField(db_column='AvgVote', blank=True, null=True) # Nom du champ en minuscules.
commentsamount = models.IntegerField(db_column='CommentsAmount', blank=True, null=True) # Nom du champ en minuscules.
created = models.DateTimeField(db_column='Created', blank=True, null=True) # Nom du champ en minuscules.
deleted = models.BooleanField(db_column='Deleted', blank=True, null=True) # Nom du champ en minuscules.
favscount = models.IntegerField(db_column='FavsCount', blank=True, null=True) # Nom du champ en minuscules.
hascorrectanswer = models.BooleanField(db_column='HasCorrectAnswer', blank=True, null=True) # Nom du champ en minuscules.
hash = models.CharField(db_column='Hash', max_length=50, blank=True, null=True) # Nom du champ en minuscules.
lang = models.CharField(db_column='Lang', max_length=50, blank=True, null=True) # Nom du champ en minuscules.
name = models.CharField(db_column='Name', max_length=250, blank=True, null=True) # Nom du champ en minuscules.
nid = models.IntegerField(db_column='Nid', primary_key=True) # Nom du champ en minuscules.
posttype = models.CharField(db_column='PostType', max_length=50, blank=True, null=True) # Nom du champ en minuscules.
published = models.BooleanField(db_column='Published', blank=True, null=True) # Nom du champ en minuscules.
publisheddate = models.DateTimeField(db_column='PublishedDate', blank=True, null=True) # Nom du champ en minuscules.
subscount = models.IntegerField(db_column='SubsCount', blank=True, null=True) # Nom du champ en minuscules.
tags = models.CharField(db_column='Tags', max_length=350, blank=True, null=True) # Nom du champ en minuscules.
text = models.CharField(db_column='Text', max_length=-1, blank=True, null=True) # Nom du champ en minuscules.
translated = models.BooleanField(db_column='Translated', blank=True, null=True) # Nom du champ en minuscules.
type = models.CharField(db_column='Type', max_length=50, blank=True, null=True) # Nom du champ en minuscules.
views = models.IntegerField(db_column='Views', blank=True, null=True) # Nom du champ en minuscules.
votesamount = models.IntegerField(db_column='VotesAmount', blank=True, null=True) # Nom du champ en minuscules.
classe Meta:
managed = False
db_table = 'community.post'
Elle est marquée comme **managed = False**, ce qui signifie que makemigrations et migrate ne fonctionneront pas pour cette table. En omettant le nom du tableau, vous obtiendrez une grande liste de modules, y compris les tableaux déjà créés par Django auparavant
Article
Iryna Mykhailova · Juin 27, 2022
Une question a été posée dans la communauté des développeurs d'InterSystems concernant la possibilité de créer une interface TWAIN pour une application Caché. Il y a eu plusieurs suggestions intéressantes sur la façon d'obtenir des données d'un périphérique d'acquisition d'images sur un client Web vers un serveur, puis de stocker ces données dans une base de données
Toutefois, pour mettre en œuvre l'une de ces suggestions, vous devez être en mesure de transférer des données d'un client Web vers un serveur de base de données et de stocker les données reçues dans une propriété de classe (ou une cellule de tableau, comme c'était le cas dans la question). Cette technique peut être utile non seulement pour transférer des données d'images provenant d'un périphérique TWAIN, mais aussi pour d'autres tâches telles que l'organisation d'une archive de fichiers, d'un partage d'images, etc.
Ainsi, l'objectif principal de cet article est de montrer comment écrire un service RESTful pour obtenir des données du corps d'une commande HTTP POST, soit à l'état brut, soit enveloppées dans une structure JSON.
Principes de base de REST
Avant d'entrer dans les détails, permettez-moi de dire quelques mots sur REST en général et sur la façon dont les services RESTful sont créés dans IRIS.
Le transfert d'état représentationnel (REST) est un style architectural pour les systèmes hypermédia distribués. L'abstraction clé de l'information dans REST est une ressource qui a son propre identifiant et qui peut être représentée dans un format JSON, XML ou un autre format connu à la fois du serveur et du client.
En général, HTTP est utilisé pour transférer des données entre le client et le serveur. Chaque opération CRUD (création, lecture, mise à jour, suppression) a sa propre méthode HTTP (POST, GET, PUT, DELETE), tandis que chaque ressource ou collection de ressources a son propre URI.
Dans cet article, je n'utiliserai que la méthode POST pour insérer une nouvelle valeur dans la base de données, et j'ai donc besoin de connaître ses contraintes.
Selon la spécification [IETF RFC7231 4.3.3 Post](https://tools.ietf.org/html/rfc7231#section-4.3.3) POST n'a pas de limite quant à la taille des données stockées dans son corps. Mais différents serveurs web et navigateurs imposent leurs propres limites, généralement de 1 Mo à 2 Go. Par exemple, [Apache](https://httpd.apache.org/docs/2.4/mod/core.html#limitrequestbody) autorise un maximum de 2 Go. Dans tous les cas, si vous devez envoyer un fichier de 2 Go, vous devriez peut-être reconsidérer votre approche.
Exigences pour un service RESTful dans IRS
Dans IRIS, pour mettre en œuvre un service RESTful, vous devez :
Créer une classe broker qui étend la classe abstraite %CSP.REST. Celle-ci, à son tour, étend %CSP.Page, ce qui permet d'accéder à différentes méthodes, paramètres et objets utiles, en particulier %request).
Spécifier UrlMap pour définir les itinéraires.
En option, définir le paramètre UseSession pour préciser si chaque appel REST est exécuté dans sa propre session Web ou s'il partage une seule session avec d'autres appels REST.
Fournir des méthodes de classe pour effectuer les opérations définies dans les itinéraires
Définissez l'application web CSP et spécifiez sa sécurité sur la page de l'application web (Administration système > Sécurité > Applications > Applications web), où la classe Dispatch doit contenir le nom de la classe utilisateur et Name, la première partie de l'URL pour l'appel REST.
En général, il existe plusieurs façons d'envoyer de gros volumes de données (fichiers) et leurs métadonnées du client au serveur, par exemple :
Coder le fichier et les métadonnées en base64 et ajouter des frais de traitement au serveur et au client pour le codage/décodage.
Envoyer d'abord le fichier et renvoyez un ID au client, qui envoie ensuite les métadonnées contenant l'ID. Le serveur réassocie le fichier et les métadonnées.
Envoyer d'abord le fichier et renvoyez un ID au client, qui envoie ensuite le fichier contenant l'ID. Le serveur réassocie le fichier et les métadonnées.
Dans ce premier article, je vais essentiellement utiliser la deuxième approche, mais sans renvoyer l'ID au client et en ajoutant les métadonnées (le nom du fichier à stocker comme une autre propriété) car cette tâche n'a rien d'exceptionnel. Dans un deuxième article, j'utiliserai la première approche, mais j'empaqueterai mon fichier et les métadonnées (le nom du fichier) dans une structure JSON avant de l'envoyer au serveur.
Mise en œuvre du service RESTFul
Passons maintenant aux détails. Tout d'abord, définissons la classe, dont nous allons définir les propriétés :
Class RestTransfer.FileDesc Extends %Persistent
{
Property File As %Stream.GlobalBinary;
Property Name As %String;
}
Bien sûr, vous aurez généralement plus de métadonnées pour accompagner le fichier, mais cela devrait suffire pour nos besoins.
Ensuite, nous devons créer un service broker de classe, que nous développerons plus tard avec des itinéraires et des méthodes :
Class RestTransfer.Broker Extends %CSP.REST
{
XData UrlMap
{
}
}
Et enfin, pour la configuration préliminaire, nous devons spécifier cette application dans une liste d'applications web :
Maintenant que la configuration préliminaire est faite, nous pouvons écrire des méthodes pour enregistrer un fichier reçu d'un client REST (j'utiliserai le client [Advanced REST Client](https://install.advancedrestclient.com/install)) dans une base de données en tant que propriété File d'une instance de la classe RestTransfer.FileDesc.
Nous allons commencer par travailler avec des informations stockées sous forme de données brutes dans le corps de la méthode POST. Tout d'abord, ajoutons un nouvel itinéraire à UrlMap :
Cet itinéraire spécifie que lorsque le service reçoit une commande POST avec l'URL /RestTransfer/file, il doit lancer la méthode de classe InsertFileContents. Pour faciliter les choses, à l'intérieur de cette méthode, je vais créer une nouvelle instance de la classe RestTransfer.FileDesc et définir sa propriété fichier File sur les données reçues. Cela renvoie le statut et un message formaté en JSON indiquant le succès ou l'erreur. Voici la méthode de classe :
ClassMethod InsertFileContents() As %Status
{
Set result={}
Set st=0
set f = ##class(RestTransfer.FileDesc).%New()
if (f = $$$NULLOREF) {
do result.%Set("Message","Couldn't create an instance of the class")
} else {
set st = f.File.CopyFrom(%request.Content)
If $$$ISOK(st) {
set st = f.%Save()
If $$$ISOK(st) {
do result.%Set("Status","OK")
} else {
do result.%Set("Message",$system.Status.GetOneErrorText(st))
}
} else {
do result.%Set("Message",$system.Status.GetOneErrorText(st))
}
}
write result.%ToJSON()
Quit st
}
Tout d'abord, une nouvelle instance de la classe RestTransfer.FileDesc est créée et on vérifie qu'elle a été créée avec succès et que nous avons un OREF. Si l'objet n'a pas pu être créé, nous formons une structure JSON :
{"Message", "Couldn't create an instance of class"}
Si l'objet a été créé, le contenu de la requête est copié dans la propriété fichier File. Si la copie n'a pas réussi, nous formons une structure JSON avec une description de l'erreur :
{"Message",$system.Status.GetOneErrorText(st)}
Si le contenu a été copié, nous sauvegardons l'objet, et si la sauvegarde est réussie, nous formons un JSON {"Status","OK"}. Dans le cas contraire, le JSON renvoie une description de l'erreur.
Enfin, nous écrivons le JSON dans une réponse et retournons l'état st.
Voici un exemple de transfert d'une image :
La manière dont il est sauvegardé dans la base de données :
Nous pouvons enregistrer ce flux dans un fichier et constater qu'il a été transféré sans être modifié :
set f = ##class(RestTransfer.FileDesc).%OpenId(4)
set s = ##class(%Stream.FileBinary).%New()
set s.Filename = "D:\Downloads\test1.jpg"
do s.CopyFromAndSave(f.File)
La même chose peut être faite avec un fichier texte :
Cela sera également visible dans les globales :
Et nous pouvons stocker des fichiers exécutables ou tout autre type de données :
Nous pouvons vérifier la dimension dans les globales, et c'est correct :
Maintenant que cette partie fonctionne comme il se doit, examinons la deuxième approche - obtenir le contenu du fichier et ses métadonnées (le nom du fichier) stockés au format JSON et transférés dans le corps d'une méthode POST.
Vous pouvez en savoir plus sur la création de services REST dans la[ documentation](https://docs.intersystems.com/iris20191/csp/docbook/Doc.View.cls?KEY=GREST) InterSystems.
Le code exemplaire pour les deux approches se trouve sur [GitHub](https://github.com/Gra-ach/RESTFileTransfer) et [InterSystems Open Exchange](https://openexchange.intersystems.com/package/RESTFileTransfer). Si vous avez des questions ou des suggestions concernant l'une ou l'autre, n'hésitez pas à les écrire dans la section des commentaires.
Annonce
Irène Mykhailova · Juin 17, 2022
Bonjour et bienvenue sur la version de mai 2022 de la Communauté des Développeurs !
Nous avons récemment amélioré votre expérience dans la communauté d'InterSystems :
🆕 Amélioration du suivi des événements actuels
🆕 Publication planifiée
🆕 Formatage de code amélioré
🆕 Création de table plus rapide
🆕 Expérience de réponse enrichie
🆕 Modification de la conception du post-teaser
Examinons de plus près tout cela ci-dessous.
LIVE NOW pour les événement
Pour faciliter et rendre plus pratique la recherche de l'événement actuellement organisé, nous avons ajouté une nouvelle section LIVE NOW dans le coin supérieur droit de la page.
Et si vous cliquez dessus, vous serez redirigé vers la page de l'événement où vous pourrez choisir de le rejoindre (s'il se tient en ligne, bien sûr).
Pour profiter de cette fonctionnalité, votre annonce de l'événement doit respecter les conditions :
Créez votre événement comme d'habitude [ajoutez la balise Événements -> des champs spéciaux apparaîtront automatiquement]
Vérifiez que votre événement a une heure de début et une heure de fin spécifiques (les événements d'une journée entière ne sont pas présentés dans cette section)
Pour permettre aux personnes de rejoindre votre événement en ligne directement à partir de cette barre, ajoutez un lien direct pour participer
Ça y est, tout est prêt !
Planifiez votre publication
Vous avez demandé - nous l'avons fait! Vous pouvez désormais planifier la publication de votre article à une heure précise.
Pour programmer votre publication pour plus tard, il vous suffit de cliquer sur la flèche vers le bas près du bouton Publier et de choisir Programmer la publication.
Après cela, vous pourrez définir la date et l'heure de publication de votre message.
Une fois que vous avez choisi l'heure, cliquez sur le bouton Programmer une publication. Votre message sera publié à l'heure sélectionnée - c'est tout !
Formatage de code
Pour partager votre code avec d'autres, nous avons ajouté un éditeur intégré lors de l'insertion de code.
Dans celui-ci, vous pouvez sélectionner le langage de programmation et la taille d'onglet appropriés.
De plus, la coloration syntaxique se produit immédiatement lors de l'écriture du texte et le langage de programmation est affiché dans le coin supérieur gauche.
En conséquence, dans votre message, vous obtiendrez un code magnifiquement formaté dans le langage de programmation souhaité.
Création de table
Pour simplifier la mise en forme lors de la création d'une table, nous avons ajouté une fonction permettant de créer rapidement une table en sélectionnant le nombre de cellules requis.
Si vous cliquez sur le bouton Plus en bas, ouvre la fonctionnalité standard de création d'une table.
Réponses et abonnements
Pour voir toutes les informations sur la discussion en un coup d'œil, nous avons ajouté le nombre de réponses en haut de l'éditeur et l'icône pour s'abonner à une discussion pour poster une discussion [nouvelles réponses].
Conception du post-teaser
Pour rendre le bas de la publication plus équilibré, nous avons réorganisé et légèrement modifié les icônes.
Profitez de nos mises à jour !
Soumettez vos demandes d'améliorations et vos rapports de bogues sur DC GitHub. Ou postez vos suggestions dans les commentaires de cet article.
À la prochaine fois avec des changements encore plus sympas !